How to Install an SSL Certificate on Nginx — Step-by-Step

Nginx is one of the world's most popular web servers, but installing an SSL certificate on it differs noticeably from Apache. This guide covers both a commercial SSL certificate (DV/OV) purchased from a CA and a free Let's Encrypt certificate via Certbot — including HTTP-to-HTTPS redirects and automatic renewal.

Prerequisites: Ubuntu 20.04/22.04 VPS with Nginx installed, a domain whose DNS A record already points to this server's IP, and root or sudo access.

Section 1 — Before You Start: What You Need

Verify these items before installing SSL:

Section 2 — Install a Commercial SSL Certificate on Nginx

After purchasing SSL from a CA (e.g. RapidSSL, DigiCert), you receive certificate files by email. Follow these steps:

2.1 Upload and Store Certificate Files

# Create a secure directory for the SSL files
sudo mkdir -p /etc/nginx/ssl/example.com

# Copy your certificate and key files
sudo cp your_domain.crt /etc/nginx/ssl/example.com/fullchain.pem
sudo cp your_private.key /etc/nginx/ssl/example.com/privkey.pem

# If the CA provided an intermediate cert, bundle them together
cat your_domain.crt intermediate.crt > /etc/nginx/ssl/example.com/fullchain.pem

# Secure the permissions
sudo chmod 600 /etc/nginx/ssl/example.com/privkey.pem
sudo chmod 644 /etc/nginx/ssl/example.com/fullchain.pem

2.2 Configure the Nginx Server Block

sudo nano /etc/nginx/sites-available/example.com
server {
    listen 443 ssl;
    server_name example.com www.example.com;

    ssl_certificate     /etc/nginx/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;

    # Secure SSL protocols only
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

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

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

# Redirect HTTP → HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

# Test the config before reloading
sudo nginx -t

# If the test passes, reload
sudo systemctl reload nginx

Section 3 — Install Let's Encrypt on Nginx with Certbot

Let's Encrypt provides free, production-ready SSL. Certbot handles everything automatically — from requesting the certificate to updating your server block.

3.1 Install Certbot

sudo apt update
sudo apt install certbot python3-certbot-nginx -y

3.2 Obtain a Certificate Automatically

# Certbot will edit your Nginx config and set up the redirect automatically
sudo certbot --nginx -d example.com -d www.example.com

Certbot asks for your email address and agreement to terms, then performs an ACME challenge to verify domain ownership. On success it updates your Nginx configuration to serve HTTPS immediately.

Tip: The --nginx plugin automatically edits your server block, sets SSL paths, and adds an HTTP-to-HTTPS redirect — no manual configuration needed.

Section 4 — Set Up HTTP→HTTPS Redirect Manually

If you prefer to control the redirect yourself without letting Certbot modify your config, add this server block:

server {
    listen 80;
    server_name example.com www.example.com;
    # 301 = Permanent Redirect (good for SEO)
    return 301 https://$host$request_uri;
}

⚠️ Avoid Redirect Loops: Never put return 301 inside the listen 443 server block. Always keep port 80 and port 443 in separate server blocks.

Section 5 — Verify SSL with openssl

After installation, confirm SSL is working correctly:

# Inspect the certificate being served
openssl s_client -connect example.com:443 -servername example.com

# Check expiry dates
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# Quick HTTP test with curl
curl -I https://example.com

Confirm the response shows HTTP/2 200 with no certificate errors. For a full security grade, run your domain through SSL Labs (ssllabs.com/ssltest).

Section 6 — Enable Auto-Renewal for Let's Encrypt

Let's Encrypt certificates expire every 90 days. Certbot installs a systemd timer or cron job automatically. Verify it works:

# Dry-run to test renewal without actually renewing
sudo certbot renew --dry-run

# Check the timer Certbot registered
systemctl list-timers | grep certbot

Best practice: Add a deploy hook so Nginx reloads after every renewal: sudo certbot renew --deploy-hook "systemctl reload nginx"

Install Let's Encrypt with Certbot — Detailed Walkthrough

If you want to understand exactly how Certbot works, or you hit a snag during the automated flow, the steps below explain everything from start to a working certificate on Nginx. Let's Encrypt is a free certificate trusted by every major browser, making it ideal for general websites that need HTTPS at no cost.

Step 1 — Prepare a Basic Port-80 Server Block

Before running Certbot you need a server block that answers on port 80 with a server_name matching your domain, because Certbot uses the HTTP-01 challenge to drop a verification file into your webroot over port 80.

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
# Test the config and reload so Nginx serves this server_name
sudo nginx -t && sudo systemctl reload nginx

Step 2 — Run Certbot in Automatic Mode

The --nginx plugin reads your Nginx config, finds the server block whose server_name matches the domain, and rewrites it to serve HTTPS automatically:

sudo certbot --nginx -d example.com -d www.example.com \
  --agree-tos -m [email protected] --redirect

Step 3 — Webroot Mode (When You Manage the Config Yourself)

If you prefer to keep full control of your Nginx config, use webroot mode so Certbot only obtains the certificate and never touches your config:

sudo certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com

The certificate files land in /etc/letsencrypt/live/example.com/, containing fullchain.pem (certificate + chain) and privkey.pem (private key), which you reference directly in your server block.

Tip: Unlike standalone mode, which needs port 80 free, webroot mode does not require stopping Nginx during issuance — making it ideal for a production server that is already running.

Install a Purchased SSL (Full Chain) on Nginx in Detail

A commercial SSL from a CA (DV/OV/EV) usually arrives as several files: the domain certificate (.crt), the intermediate/chain certificate (.ca-bundle or chain.crt), and the private key (.key) generated with your CSR. The most important thing on Nginx is that you must combine the domain certificate and the intermediate into a single full-chain file — otherwise you get an incomplete chain.

Build the Full Chain in the Correct Order

Order matters: the domain certificate must come first, followed by the intermediate (never include the root CA):

# Order: domain cert first, then intermediate
cat your_domain.crt intermediate.ca-bundle > fullchain.pem

# Move into place and lock down permissions
sudo mv fullchain.pem /etc/nginx/ssl/example.com/fullchain.pem
sudo cp your_private.key /etc/nginx/ssl/example.com/privkey.pem
sudo chmod 600 /etc/nginx/ssl/example.com/privkey.pem

Reference It in the Server Block

Point ssl_certificate at the full-chain file and ssl_certificate_key at the private key, with recommended security values:

server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

    ssl_certificate     /etc/nginx/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

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

⚠️ Never include the root CA in the full-chain file: some CAs ship a root certificate too, but browsers already have roots in their trust store. Adding the root only bloats the handshake. Include only the domain certificate plus the intermediate.

Force HTTPS with return 301 + Enable HSTS on Nginx

After installing SSL, the key step is to force every visitor onto HTTPS. Do this in two layers: first redirect all port-80 requests with return 301, then enable HSTS (HTTP Strict Transport Security) so the browser remembers to use HTTPS only — even if the user types http://.

Redirect HTTP→HTTPS with return 301

# A separate server block for port 80 only
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

Enable HSTS in the listen-443 Block

server {
    listen 443 ssl;
    server_name example.com www.example.com;

    # Force HTTPS for one year, including subdomains
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # ... ssl_certificate and other values as above ...
}

⚠️ Enable HSTS carefully: once a browser receives the HSTS header it "locks" to HTTPS for the max-age duration. If your SSL expires or you remove HTTPS during that window, the site becomes unreachable. Start with a short max-age (for example max-age=300), then extend to one year, and do not add preload until every subdomain supports HTTPS.

Test, Renew, and Common Issues

Once installed, test the chain and the connection thoroughly, set up renewal, and know how to fix the issues that come up most often on Nginx.

Test the Chain and Expiry

# Verify the chain is complete (no "unable to get local issuer")
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -i "verify"

# Check expiry dates
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

Auto-Renew Let's Encrypt

# Dry-run renewal and reload Nginx automatically on success
sudo certbot renew --dry-run
sudo certbot renew --deploy-hook "systemctl reload nginx"

Common Problems and Fixes

Symptom Cause + Fix
Chain incomplete / unable to get local issuer The intermediate is missing from ssl_certificate — bundle domain cert + intermediate into fullchain.pem, then reload.
Cannot reach https / connection refused Port 443 is not open in the firewall — run ufw allow 443 and confirm listen 443 ssl; is in the server block.
Mixed content (no green padlock) The page loads resources over http:// — change image/CSS/JS links to https:// or relative paths.
Redirect loop (ERR_TOO_MANY_REDIRECTS) A return 301 sits inside the listen 443 block — move it into the port-80 block only.

SSL on Nginx — Setup Checklist

  1. Confirm DNS A record points to your server IP.
  2. Open ports 80 and 443 in UFW.
  3. Store certificate and key files with correct permissions (600/644).
  4. Set ssl_certificate and ssl_certificate_key in the server block.
  5. Add a separate port-80 server block for the HTTP redirect.
  6. Always run nginx -t before reloading.
  7. Test with openssl s_client or SSL Labs.
  8. For Let's Encrypt: set a --deploy-hook to reload Nginx automatically after renewal.

Frequently Asked Questions (FAQ)

How does free Let's Encrypt differ from a purchased SSL on Nginx?

The Nginx setup is almost identical — both use ssl_certificate and ssl_certificate_key. The difference is that Let's Encrypt is free, issues DV certificates valid for 90 days with automatic renewal, while a purchased SSL offers DV/OV/EV options valid for one year with a CA warranty. AsiaGB DV certificates start at 1,000 THB/year, and free Let's Encrypt also works on our servers.

Why does the browser still warn about an incomplete chain after install?

Because Nginx's ssl_certificate must point to a file that combines the domain certificate and the intermediate (the full chain) — unlike Apache, which can reference a separate chain file. Run cat your_domain.crt intermediate.crt > fullchain.pem, point ssl_certificate at that file, then reload Nginx.

Do I need to restart or reload Nginx after changing the certificate?

sudo systemctl reload nginx is enough — no restart needed. Reload loads the new config and certificate without dropping active connections, and you should always run sudo nginx -t to check syntax before reloading.

How do I avoid forgetting that certificates expire every 90 days?

Certbot installs a systemd timer automatically at install time; check it with systemctl list-timers | grep certbot, and add --deploy-hook "systemctl reload nginx" so Nginx loads the new certificate immediately after renewal. For purchased one-year SSL, AsiaGB sends expiry reminders ahead of time.

SSL Certificates for Your Website

AsiaGB offers RapidSSL, GeoTrust, and DigiCert certificates starting at 1,000 THB/year. Trusted by global CAs, easy to install, with expert support.

View SSL Plans