🌐

SSL

20 notes  •  Web Hosting

Create an SSL Bundle File from Comodo/Sectigo CRT Files

When you receive a Comodo or Sectigo SSL certificate, you get multiple .crt files. You need to concatenate them into a bundle in the correct order.

Certificate Chain Order (Root → Intermediate → Domain)

  1. AddTrustExternalCARoot.crt — Root CA
  2. ComodoRSAAddTrustCA.crt — Intermediate CA 1
  3. ComodoRSADomainValidationSecureServerCA.crt — Intermediate CA 2
  4. yourdomain.crt — Your domain certificate

Create the Bundle via Command Line

# Concatenate intermediates + root into ca-bundle
cat ComodoRSAAddTrustCA.crt ComodoRSADomainValidationSecureServerCA.crt AddTrustExternalCARoot.crt > yourdomain.ca-bundle

# Or create a single ssl-bundle (domain cert + chain)
cat yourdomain.crt yourdomain.ca-bundle > ssl-bundle.crt

Create the Bundle in a Text Editor

Open each file and paste them into a single file in this order (each block must start with -----BEGIN CERTIFICATE-----):

-----BEGIN CERTIFICATE-----
[your domain certificate]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[intermediate CA 1]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[intermediate CA 2]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[root CA]
-----END CERTIFICATE-----

Verify the Bundle

openssl verify -CAfile yourdomain.ca-bundle yourdomain.crt

Install SSL Certificate on Apache httpd with mod_ssl

How to install a commercial SSL certificate on Apache using mod_ssl (CentOS/RHEL/Ubuntu).

Step 1 — Copy Certificate Files to Server

sudo mkdir -p /etc/ssl/certs/example.com
sudo cp yourdomain.crt  /etc/ssl/certs/example.com/
sudo cp yourdomain.ca-bundle /etc/ssl/certs/example.com/
sudo cp yourdomain.key  /etc/ssl/private/example.com.key
sudo chmod 600 /etc/ssl/private/example.com.key

Step 2 — Enable mod_ssl

# Ubuntu/Debian
sudo a2enmod ssl
sudo systemctl restart apache2

# CentOS/RHEL
sudo yum install mod_ssl
sudo systemctl restart httpd

Step 3 — Configure the SSL VirtualHost


    ServerName example.com
    DocumentRoot /var/www/html/example.com

    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/example.com/yourdomain.crt
    SSLCertificateKeyFile   /etc/ssl/private/example.com.key
    SSLCertificateChainFile /etc/ssl/certs/example.com/yourdomain.ca-bundle

    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

Step 4 — Test and Restart

apachectl configtest
sudo systemctl restart apache2
openssl s_client -connect example.com:443 -brief

Secure Apache with Let's Encrypt on Ubuntu

How to obtain and install a free Let's Encrypt SSL certificate for an Apache site on Ubuntu using Certbot.

Prerequisites

  • Apache installed with a domain pointing to the server
  • Port 80 open in firewall

Step 1 — Install Certbot

sudo apt-get update
sudo apt-get install -y certbot python3-certbot-apache

Step 2 — Obtain and Install Certificate

sudo certbot --apache -d example.com -d www.example.com

Certbot will automatically configure the Apache VirtualHost and set up HTTPS redirect.

Step 3 — Verify Auto-Renewal

sudo certbot renew --dry-run

Step 4 — Check Renewal Timer

systemctl status certbot.timer
cat /etc/cron.d/certbot

Verify

curl -I https://example.com
openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

Obtain Let's Encrypt Certificate with Webroot Plugin on Apache

The webroot plugin verifies domain ownership via a file placed in the web root, without stopping Apache.

Step 1 — Configure the Webroot Alias in Apache

# /etc/apache2/conf-available/letsencrypt.conf
Alias /.well-known/acme-challenge/ "/var/www/html/.well-known/acme-challenge/"
<Directory "/var/www/html/.well-known/acme-challenge/">
    Options None
    AllowOverride None
    Require all granted
</Directory>
sudo a2enconf letsencrypt
sudo systemctl reload apache2

Step 2 — Obtain the Certificate

sudo certbot certonly --webroot   -w /var/www/html   -d example.com   -d www.example.com   --email admin@example.com   --agree-tos

Step 3 — Add SSL to Apache VirtualHost


    ServerName example.com
    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

Step 4 — Enable SSL and Restart

sudo a2enmod ssl
sudo systemctl restart apache2

Create PEM Files from Comodo CRT Files

How to convert Comodo/Sectigo CRT files to PEM format for use with Apache, Nginx, or other services.

Create Private Key PEM

openssl rsa -in server.key -text > private.pem

Create Public Certificate PEM

openssl x509 -inform PEM -in server.crt > public.pem

Create CA Bundle from Comodo Files

cat ComodoRSAAddTrustCA.crt     ComodoRSADomainValidationSecureServerCA.crt     AddTrustExternalCARoot.crt     > yourDomain.ca-bundle

Verify the Certificate

openssl verify -CAfile yourDomain.ca-bundle server.crt
openssl x509 -in server.crt -text -noout | grep -E "Subject:|Issuer:|Not"

Obtain Let's Encrypt SSL on GoDaddy Shared Hosting (No Root)

Getting a Let's Encrypt certificate on GoDaddy shared hosting requires using acme.sh with DNS verification since you don't have root access or the ability to run standalone services.

Step 1 — Install acme.sh

curl https://get.acme.sh | sh
source ~/.bashrc

Step 2 — Issue via DNS Challenge (GoDaddy)

# Set GoDaddy API credentials
export GD_Key="your_godaddy_api_key"
export GD_Secret="your_godaddy_api_secret"

# Issue the certificate using DNS
~/.acme.sh/acme.sh --issue -d MYDOMAIN.com -d www.MYDOMAIN.com --dns dns_gd

Step 3 — Install the Certificate

# Install to a target directory
~/.acme.sh/acme.sh --install-cert -d MYDOMAIN.com   --cert-file ~/ssl/cert.pem   --key-file ~/ssl/key.pem   --fullchain-file ~/ssl/fullchain.pem

Step 4 — Upload to GoDaddy cPanel

  1. Log into cPanel → SSL/TLS
  2. Under Install and Manage SSL, paste the certificate, key, and CA bundle
  3. Click Install Certificate

Notes

  • GoDaddy API keys can be generated at developer.godaddy.com
  • acme.sh auto-renews certificates via a cron job added during install

Set Up Let's Encrypt SSL with Docker and Nginx (Automated)

How to automate Let's Encrypt certificate issuance and renewal for Nginx running inside Docker using Certbot.

Docker Compose Setup

# docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    depends_on:
      - certbot

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    entrypoint: >
      sh -c "certbot certonly --webroot
        -w /var/www/certbot
        -d example.com
        --email admin@example.com
        --agree-tos --non-interactive
      && while :; do sleep 12h; certbot renew; done"

Initial Nginx Config (HTTP Only for First Run)

# nginx/conf.d/app.conf
server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Start, Issue Certificate, Then Enable HTTPS

docker-compose up -d
# Wait for Certbot to issue the cert, then add SSL server block to nginx config
docker-compose exec nginx nginx -s reload

Fix openssl 'error 18 at 0 depth lookup' When Creating Self-Signed Certificates

When creating a self-signed CA and server certificate for MySQL or other services, OpenSSL may show error 18 (self-signed certificate). This is expected behavior for self-signed certs and can be ignored, but here is the proper workflow.

Create a Self-Signed CA and Server Certificate

# Step 1: Create the CA key and certificate
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 3600   -key ca-key.pem   -out ca.pem

# Step 2: Create the server key and CSR
openssl req -newkey rsa:2048 -days 3600   -nodes -keyout server-key.pem   -out server-req.pem

# Step 3: Sign the server CSR with the CA
openssl rsa -in server-key.pem -out server-key.pem
openssl x509 -req -in server-req.pem -days 3600   -CA ca.pem -CAkey ca-key.pem   -set_serial 01   -out server-cert.pem

# Step 4: Verify (error 18 here is normal for self-signed)
openssl verify -CAfile ca.pem server-cert.pem

Notes

  • error 18 at 0 depth lookup: self-signed certificate is not a failure — it simply means the CA cert is self-signed (not signed by a trusted root). This is expected.
  • For MySQL SSL, use these files: ca.pem, server-cert.pem, server-key.pem

Let's Encrypt Wildcard Certificate with AWS Route 53

How to issue a Let's Encrypt wildcard certificate using DNS challenge with AWS Route 53.

Prerequisites

  • Domain managed in AWS Route 53
  • AWS CLI configured with Route 53 permissions
  • Certbot with Route 53 plugin

Step 1 — Install Certbot and Route 53 Plugin

sudo apt-get install -y certbot python3-certbot-dns-route53

Step 2 — Configure AWS Credentials

# ~/.aws/credentials
[default]
aws_access_key_id = YOUR_KEY
aws_secret_access_key = YOUR_SECRET

Step 3 — Issue the Wildcard Certificate

sudo certbot certonly   --dns-route53   -d "example.com"   -d "*.example.com"   --email admin@example.com   --agree-tos   --non-interactive

Step 4 — Use in Nginx

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

Step 5 — Verify Auto-Renewal

sudo certbot renew --dry-run

Issue a Wildcard SSL Certificate with Certbot (DNS Challenge)

How to obtain a Let's Encrypt wildcard certificate using Certbot with manual DNS challenge.

Linux — Manual DNS Challenge

sudo certbot certonly   --manual   --preferred-challenges=dns   --email admin@example.com   --server https://acme-v02.api.letsencrypt.org/directory   --agree-tos   -d "example.com"   -d "*.example.com"

Certbot will prompt you to add a TXT record to your DNS. Add it, then press Enter to continue.

Verify the DNS Record Before Continuing

dig TXT _acme-challenge.example.com +short

With Auto-Renewal via DNS Plugin (e.g., Cloudflare)

sudo apt-get install -y python3-certbot-dns-cloudflare

# Create credentials file
cat > /etc/letsencrypt/cloudflare.ini << EOF
dns_cloudflare_email = admin@example.com
dns_cloudflare_api_key = YOUR_API_KEY
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini

sudo certbot certonly   --dns-cloudflare   --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini   -d "example.com" -d "*.example.com"

Let's Encrypt SSL with Certbot — Quick Reference

Quick reference for the most common Certbot commands for issuing and managing Let's Encrypt certificates.

Install Certbot

# Ubuntu/Debian
sudo apt-get install -y certbot python3-certbot-nginx python3-certbot-apache

# CentOS/RHEL
sudo dnf install -y certbot python3-certbot-nginx

Issue Certificate (Nginx)

sudo certbot --nginx -d example.com -d www.example.com

Issue Certificate (Apache)

sudo certbot --apache -d example.com -d www.example.com

Issue Certificate (Standalone — Stops Web Server)

sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com
sudo systemctl start nginx

Renew All Certificates

sudo certbot renew
sudo certbot renew --dry-run   # Test without renewing

List Certificates

sudo certbot certificates

Revoke a Certificate

sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem

Certificate File Locations

/etc/letsencrypt/live/example.com/fullchain.pem  # Cert + chain
/etc/letsencrypt/live/example.com/privkey.pem     # Private key
/etc/letsencrypt/live/example.com/cert.pem        # Cert only
/etc/letsencrypt/live/example.com/chain.pem       # Chain only

Certbot SSL Certificate with Cloudflare (Webroot Method)

When using Cloudflare, the DNS proxy can interfere with Certbot's HTTP challenge. Here is how to issue a certificate using the webroot method while Cloudflare is active.

Prerequisites

  • Nginx or Apache serving the domain
  • Certbot installed
  • Cloudflare set to DNS only (grey cloud) during cert issuance, OR use DNS challenge

Issue Certificate via Webroot

sudo certbot certonly --webroot   --webroot-path /var/www/html   --renew-by-default   --email admin@example.com   --text --agree-tos   -d example.com   -d www.example.com   -d subdomain.example.com

Issue Certificate via Cloudflare DNS Plugin (Recommended)

sudo apt-get install -y python3-certbot-dns-cloudflare

cat > /etc/letsencrypt/cloudflare.ini << EOF
dns_cloudflare_email = admin@example.com
dns_cloudflare_api_key = YOUR_GLOBAL_API_KEY
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini

sudo certbot certonly --dns-cloudflare   --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini   -d example.com -d "*.example.com"

Reload Nginx After Renewal

# /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh

Obtain Let's Encrypt Certificate Using Certbot Standalone

The standalone method briefly starts its own web server on port 80 to verify domain ownership. This requires stopping any existing web server first.

Step 1 — Stop the Web Server

sudo systemctl stop nginx
# or
sudo systemctl stop apache2

Step 2 — Issue the Certificate

sudo certbot certonly --standalone   -d example.com   -d www.example.com   --email admin@example.com   --agree-tos

Step 3 — Start the Web Server

sudo systemctl start nginx

Auto-Restart After Renewal

sudo certbot certonly --standalone   --post-hook "systemctl restart nginx"   -d example.com

Set Up Auto-Renewal

sudo certbot renew --dry-run

Certbot installs a systemd timer or cron job automatically. Renewal runs twice daily and only renews when the cert is within 30 days of expiry.

Fix 'Unable to load Private Key' SSL Error (PEM encoding issue)

If Apache or Nginx fails to start with an error like PEM routines:PEM_read_bio:no start line, the private key file has incorrect encoding (often UTF-8-BOM instead of plain UTF-8).

Error Example

SSL Library Error: 218529960 error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
Unable to configure RSA server private key
PEM routines:PEM_read_bio:no start line:pem_lib.c:648:Expecting: ANY PRIVATE KEY

Fix — Convert BOM Encoding to Plain UTF-8

Open the key file in a text editor that supports encoding conversion:

  • In Notepad++: Encoding → Convert to UTF-8 (not UTF-8-BOM)
  • In VS Code: Bottom status bar shows encoding — click it and select "Save with Encoding → UTF-8"

Fix via Command Line

# Remove BOM from the key file
sed -i '1s/^//' yourdomain.key

# Or use iconv
iconv -f UTF-8-BOM -t UTF-8 yourdomain.key -o yourdomain-fixed.key

Verify the Key

openssl rsa -check -in yourdomain.key -noout
# Should output: RSA key ok

Allow .well-known Validation Files on Bitnami Apache

On Bitnami-based servers, the .well-known/pki-validation directory used for SSL domain control validation (DCV) is aliased to a specific directory.

Default Alias Path

The .well-known directory is aliased to:

/opt/bitnami/apps/letsencrypt/.well-known/

This alias is defined in:

/opt/bitnami/apps/letsencrypt/conf/httpd-app.conf

Step 1 — Create the Validation Directory

sudo mkdir -p /opt/bitnami/apps/letsencrypt/.well-known/pki-validation/

Step 2 — Upload the Validation File

sudo cp your-validation-file.txt   /opt/bitnami/apps/letsencrypt/.well-known/pki-validation/

Step 3 — Verify Access

curl http://yourdomain.com/.well-known/pki-validation/your-validation-file.txt

Alternative — Use Certbot with Bitnami

sudo /opt/bitnami/ctlscript.sh stop apache
sudo certbot certonly --standalone -d yourdomain.com
sudo /opt/bitnami/ctlscript.sh start apache

Fix 'x509: certificate signed by unknown authority' Error

This error occurs when a system doesn't trust the certificate's CA — typically because the Let's Encrypt ISRG Root X1 certificate isn't in the system's trust store (common on older systems).

Error Example

x509: certificate signed by unknown authority

Fix — Add the ISRG Root X1 Certificate

sudo curl -k -o /usr/local/share/ca-certificates/isrgrootx1.crt   https://letsencrypt.org/certs/isrgrootx1.pem

sudo update-ca-certificates

Alternative — Download Without -k Flag (More Secure)

# Download via wget using system CAs
wget --ca-certificate=/etc/ssl/certs/ca-certificates.crt   https://letsencrypt.org/certs/isrgrootx1.pem   -O /usr/local/share/ca-certificates/isrgrootx1.crt

sudo update-ca-certificates

Verify

curl https://letsencrypt.org/
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/letsencrypt/live/example.com/fullchain.pem

Notes

  • This is common on Debian Jessie, Ubuntu 14.04, and other older distros.
  • Upgrading ca-certificates package usually also fixes it: sudo apt-get install --reinstall ca-certificates

Create a Java KeyStore (JKS) from an Existing Certificate and Private Key

How to create a JKS keystore file for Java applications (Tomcat, Jenkins, etc.) from an existing SSL certificate and private key.

Step 1 — Convert Certificate and Key to PKCS12

openssl pkcs12 -export   -in fullchain.pem   -inkey privkey.pem   -out keystore.p12   -name tomcat   -password pass:changeit

Step 2 — Convert PKCS12 to JKS

keytool -importkeystore   -deststorepass changeit   -destkeypass changeit   -destkeystore keystore.jks   -srckeystore keystore.p12   -srcstoretype PKCS12   -srcstorepass changeit   -alias tomcat

Step 3 — Verify the Keystore

keytool -list -v -keystore keystore.jks -storepass changeit

Configure Tomcat to Use the JKS

# server.xml
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
    SSLEnabled="true" keystoreFile="/path/to/keystore.jks"
    keystorePass="changeit" />

Issue a Let's Encrypt Wildcard Certificate (Manual DNS Method)

How to obtain a Let's Encrypt wildcard certificate using the manual DNS challenge method.

Step 1 — Clone and Run Certbot

git clone https://github.com/certbot/certbot
cd certbot
git checkout v0.22.0    # Use latest stable tag
sudo ./certbot-auto --os-packages-only
./tools/venv.sh
source venv/bin/activate

Step 2 — Issue the Wildcard Certificate

sudo ./certbot -d domain.com -d "*.domain.com"   --manual   --preferred-challenges dns-01   --server https://acme-v02.api.letsencrypt.org/directory

Certbot will display two TXT records to add to your DNS. Add them both, verify propagation, then continue.

Verify DNS Propagation

dig TXT _acme-challenge.domain.com +short

Notes

  • Wildcard certificates require DNS-01 challenge — HTTP challenge cannot be used.
  • Manual DNS method requires updating DNS records on each renewal — consider a DNS plugin for automation.

Create Wildcard SSL Certificates with Let's Encrypt (Certbot)

Wildcard certificates (e.g., *.example.com) cover all subdomains of a domain with a single certificate. Let's Encrypt has supported wildcard certs since March 2018.

Requirements

  • Certbot 0.22.0 or newer
  • DNS challenge capability (your registrar's API or manual DNS entry)
  • DNS must be updated to prove domain ownership

Issue the Wildcard Certificate

sudo certbot certonly   --manual   --preferred-challenges dns   --server https://acme-v02.api.letsencrypt.org/directory   -d "example.com"   -d "*.example.com"   --email admin@example.com   --agree-tos

What Gets Covered

  • *.example.com covers: www.example.com, api.example.com, mail.example.com
  • It does NOT cover the bare domain example.com — add it explicitly with -d example.com
  • It does NOT cover sub.sub.example.com (only one level deep)

Install in Nginx

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

Sectigo SSL Certificate Concatenation Order

When installing a Sectigo (formerly Comodo) SSL certificate, the certificate files must be concatenated in the correct order.

File Order for ssl-bundle.crt

# Order: Private key → Domain cert → Intermediate(s)
# Create the bundle:

# 1. Your private key (keep this separate and secure)
-----BEGIN PRIVATE KEY-----
{private key body}
-----END PRIVATE KEY-----

# 2. Your domain certificate
-----BEGIN CERTIFICATE-----
{domain cert body}
-----END CERTIFICATE-----

# 3. Sectigo intermediate and root certificates (chain)
-----BEGIN CERTIFICATE-----
{intermediate cert 1}
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
{root cert}
-----END CERTIFICATE-----

Create the Certificate Bundle via Command Line

# Combine domain cert + intermediate chain (for Nginx)
cat yourdomain.crt SectigoRSADomainValidationSecureServerCA.crt     USERTrustRSAAAACA.crt AAACertificateServices.crt > ssl-bundle.crt

Nginx Configuration

ssl_certificate     /etc/ssl/ssl-bundle.crt;
ssl_certificate_key /etc/ssl/yourdomain.key;

Apache Configuration

SSLCertificateFile      /etc/ssl/yourdomain.crt
SSLCertificateKeyFile   /etc/ssl/yourdomain.key
SSLCertificateChainFile /etc/ssl/yourdomain.ca-bundle