SSL/TLS Certificate Installation Guide: OpenSSL Keys, Modern Nginx, and Apache

In modern web development and API operations, HTTPS (SSL/TLS encryption) is no longer an optional feature—it is a non-negotiable baseline. Unencrypted HTTP sites are flagged as “Not Secure” by all major web browsers, penalized in search engine rankings (SEO), and expose user credentials to Man-in-the-Middle (MITM) attacks.

Whether purchasing high-assurance organizational certificates (OV/EV), securing internal microservices, generating self-signed certificates for staging environments, or adopting automated certificate authorities, mastering OpenSSL fundamentals and modern Nginx/Apache configurations is essential knowledge for every systems engineer.


1. The Core Triad of SSL/TLS Certificates

Before touching any terminal commands, it is essential to understand the roles and relationships of the three core files:

  1. Private Key (.key): The server’s crown jewel. Used to decrypt symmetric session keys and produce digital signatures. Must never be shared or exposed.
  2. Certificate Signing Request (CSR, .csr): An application manifest containing your public key and organizational identity (Common Name/FQDN, organization, country). This is submitted to a Certificate Authority (CA) for verification.
  3. Digital Certificate (.crt or .pem): The signed identity document returned by the CA, cryptographically binding your public key to your verified domain name with a defined expiration date.

2. Generating Keys, CSRs, and Self-Signed Certificates with OpenSSL

1. Generating an RSA Private Key

Run the following command to generate an unencrypted 2048-bit RSA private key:

openssl genrsa -out example.com.key 2048

Why avoid encrypting the private key with a passphrase? If you add -des3 to protect the key with a passphrase, Nginx or Apache will block server reboots and automated process reloads until someone physically types the passphrase in the console. If you have an existing encrypted key, strip the passphrase using:

openssl rsa -in example.com_secure.key -out example.com.key

2. Securing Private Key Permissions

Restrict filesystem access so only the root user can read the key:

sudo chown root:root example.com.key
sudo chmod 600 example.com.key

3. Generating a Certificate Signing Request (CSR)

Generate the CSR using your private key:

openssl req -new -key example.com.key -out example.com.csr

You will be prompted for your country, state, organization, and most importantly, the Common Name (e.g. server FQDN)—enter your exact domain (e.g., example.com or wildcard *.example.com).

4. (For Local/Staging) Generating a Self-Signed Certificate

For local development, automated testing, or air-gapped private networks, you can generate both a private key and a self-signed certificate in a single command (valid for 365 days):

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout example.com.key \
  -out example.com.crt \
  -subj "/CN=example.com"

3. Modern Nginx HTTPS Configuration

In older Nginx documentation, you frequently encounter the ssl on; directive. Note that ssl on; has been officially deprecated and removed from modern versions of Nginx.

1. Modern Nginx Server Block

Edit /etc/nginx/sites-available/example.com:

# 1. Force HTTP (Port 80) to HTTPS (Port 443) redirect
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

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

# 2. Secure HTTPS (Port 443) Server Block
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    # For HTTP/2 support:
    # Nginx 1.25.1+: http2 on;
    # Older versions: listen 443 ssl http2;

    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.html index.php;

    # Fullchain certificate bundle and private key
    ssl_certificate /etc/ssl/certs/example.com_bundle.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # Modern TLS protocol hardening (disable SSLv3, TLS 1.0, and TLS 1.1)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # TLS session caching for reduced handshake latency
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    location / {
        try_files $uri $uri/ =404;
    }
}

Critical Pitfall: The Intermediate Certificate Chain (Bundle)

When purchasing commercial certificates, the CA usually provides your domain certificate (domain.crt) alongside intermediate CA certificates (intermediate.crt). In Nginx, ssl_certificate must point to a unified bundle containing the full certificate chain. If you only provide the domain certificate, curl and mobile browsers will fail with SSL certificate problem: unable to get local issuer certificate.

  • Fix: Concatenate your domain certificate and the intermediate certificate into a single file (domain cert on top, intermediate cert below):
    cat example.com.crt intermediate.crt > example.com_bundle.crt

4. Modern Apache HTTPS Configuration

On Apache servers, first ensure the SSL and header modules are enabled:

sudo a2enmod ssl
sudo a2enmod headers

1. Apache VirtualHost Configuration

Edit /etc/apache2/sites-available/example.com-ssl.conf:

# 1. Permanent HTTP to HTTPS 301 Redirect
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com

    Redirect permanent / https://example.com/
</VirtualHost>

# 2. HTTPS VirtualHost Configuration
<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com

    SSLEngine on

    # Certificate bundle and private key paths
    # Note: In Apache 2.4.8+, SSLCertificateFile accepts the complete fullchain bundle directly.
    SSLCertificateFile /etc/ssl/certs/example.com_bundle.crt
    SSLCertificateKeyFile /etc/ssl/private/example.com.key

    # For legacy Apache (< 2.4.8), intermediate certs were declared separately:
    # SSLCertificateChainFile /etc/ssl/certs/intermediate.crt

    # Restrict to modern TLS protocols
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite HIGH:!aNULL:!MD5
    SSLHonorCipherOrder on

    <Directory /var/www/example.com>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

5. The Modern Standard for Public Sites: Let’s Encrypt Automation

For publicly accessible web applications that do not require organizational vetting (OV/EV), the industry standard is Let’s Encrypt using Certbot.

Certbot automates key generation, domain validation, server configuration, and 90-day certificate renewals completely hands-free:

# Install Certbot on Debian / Ubuntu
sudo apt update && sudo apt install -y certbot python3-certbot-nginx

# Request and configure certificates automatically for Nginx
sudo certbot --nginx -d example.com -d www.example.com

# Verify automated renewal timers (handled by systemd timers)
sudo certbot renew --dry-run

6. Verification and Troubleshooting

Always validate web server configuration syntax prior to reloading daemons:

  • Nginx syntax check & graceful reload:
    sudo nginx -t
    sudo systemctl reload nginx
  • Apache syntax check & graceful reload:
    sudo apache2ctl configtest
    sudo systemctl reload apache2
  • Verify TLS Handshake via OpenSSL CLI:
    openssl s_client -connect example.com:443 -servername example.com
  • Online Deep Security Audit: Run your hostname through the Qualys SSL Labs SSL Test to confirm a hardened A+ grade.