How-to guide · Install on a server

How to install a certificate in nginx

In one sentence

nginx serves your certificate and its full chain on port 443, with a key that matches, and you have proved it from the outside with OpenSSL and curl.

Task
Install on a server
Platforms
  • Linux
  • nginx
Who it is for
Level
Practitioner · 4 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Build the full chain file
  4. 2. Check that the key matches the certificate
  5. 3. Configure the server block
  6. 4. Test the configuration and reload
  7. Check it worked
  8. If something goes wrong
  9. Why this works
  10. Go deeper

Before you start

  • Your server certificate (the leaf certificate) and the CA’s intermediate certificate or certificates, in PEM format.
  • The private key you created with the CSR. It never leaves the server; never paste it into a website.
  • Root access, and port 443 open in the firewall.
  • If you use Let’s Encrypt, certbot can write these files and the configuration for you.

Why it matters: nginx sends exactly the certificates you give it. Leave out the intermediate and most browsers still work, but curl on Linux, Java, Python, Node.js and many phones fail.

Steps

1. Build the full chain file

nginx wants the leaf first, then the intermediates, in one file. The nginx documentation says “the primary certificate comes first, then the intermediate certificates”. Leave the root out: clients must already have it. See how to build the full chain file.

sudo mkdir -p /etc/ssl/example
cat www.example.com.crt intermediate.crt | sudo tee /etc/ssl/example/fullchain.pem > /dev/null
sudo install -m 600 -o root -g root www.example.com.key /etc/ssl/example/privkey.pem

Check the order. Each certificate’s issuer must be the subject of the next one:

openssl crl2pkcs7 -nocrl -certfile /etc/ssl/example/fullchain.pem | openssl pkcs7 -print_certs -noout
subject=CN = intranet.example.com
issuer=C = BE, O = Example Corp, CN = Example Corp Issuing CA 1

subject=C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
issuer=C = BE, O = Example Corp, CN = Example Corp Root CA

2. Check that the key matches the certificate

The two hashes must be identical (key and certificate match):

openssl x509 -in /etc/ssl/example/fullchain.pem -noout -pubkey | openssl sha256
sudo openssl pkey -in /etc/ssl/example/privkey.pem -pubout | openssl sha256
SHA2-256(stdin)= 4831d810716492fe84b3e7885848156700dce2d6c52325f917f7dec47de8ba1c
SHA2-256(stdin)= 4831d810716492fe84b3e7885848156700dce2d6c52325f917f7dec47de8ba1c

openssl x509 reads only the first certificate in the file, which is why the leaf must come first.

3. Configure the server block

On Debian and Ubuntu, create /etc/nginx/sites-available/example.com and link it into sites-enabled; on other systems, use a file in /etc/nginx/conf.d/:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.example.com example.com;

    ssl_certificate     /etc/ssl/example/fullchain.pem;
    ssl_certificate_key /etc/ssl/example/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    root /var/www/example;
}

server {
    listen 80;
    listen [::]:80;
    server_name www.example.com example.com;
    return 301 https://$host$request_uri;
}

ssl_protocols TLSv1.2 TLSv1.3 is the default since nginx 1.27.3, but older packages still enable TLS 1.0 and 1.1: Ubuntu 24.04’s /etc/nginx/nginx.conf contains ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;. Change that line too, so every server block agrees. For HTTP/2, add http2 on; (nginx 1.25.1 and later) or listen 443 ssl http2; on older versions. Ciphers, HSTS and the rest are in How to configure TLS securely.

To serve an ECDSA and an RSA certificate side by side, repeat the ssl_certificate and ssl_certificate_key pair; nginx has supported that since 1.11.0. A key with a passphrase needs ssl_password_file.

4. Test the configuration and reload

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

A reload loads the new files without dropping connections. nginx reads certificates only at start and reload, so every renewal needs one; certbot and similar clients do it with a deploy hook.

Check it worked

Look at what the server actually sends, from another machine if you can:

openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null | grep -E '^ [0-9] s:|^   i:|Verify return code'

On our test server (nginx 1.24 on Ubuntu 24.04, private test CA, so we added -CAfile corp-root.crt):

 0 s:CN = intranet.example.com
   i:C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
 1 s:C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
   i:C = BE, O = Example Corp, CN = Example Corp Root CA
Verify return code: 0 (ok)

Two certificates, leaf first, and code 0. Then a plain request:

curl -sS -o /dev/null -w '%{http_code}\n' https://www.example.com/

Paste the -showcerts output into the chain checker to see the same verdict, explained:

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

If something goes wrong

  • nginx -t passes, but clients say unable to verify the first certificate or unable to get local issuer certificate. The intermediate is missing. nginx -t does not check the chain: with the leaf alone our test passed nginx -t, then openssl s_client showed one certificate and Verify return code: 21 (unable to verify the first certificate), and curl failed. Rebuild fullchain.pem (step 1).

  • key values mismatch. nginx refuses to start:

    [emerg] SSL_CTX_use_PrivateKey("/etc/ssl/example/privkey.pem") failed (SSL: error:05800074:x509 certificate routines::key values mismatch)

    Either the key belongs to another certificate, or the chain file starts with the intermediate. We got the same message both ways. Check the order and the key (steps 1 and 2).

  • cannot load certificate … BIO_new_file() failed. The path is wrong or the file is missing.

  • The browser shows the old certificate. You did not reload, or another server block answers for that name. Run sudo nginx -T | grep -E 'server_name|ssl_certificate' to see the configuration nginx actually loaded.

  • NET::ERR_CERT_COMMON_NAME_INVALID. The certificate’s Subject Alternative Name does not cover the name in the address bar. Check it with the hostname matcher.

  • NET::ERR_CERT_DATE_INVALID. The certificate has expired or is not yet valid, or the client’s clock is wrong.

Why this works

During the TLS handshake, nginx sends the certificates from ssl_certificate in file order, and signs the handshake with the matching private key to prove it holds that key. The client then builds a certificate chain from the leaf through the intermediate to a root in its own store. If the intermediate is missing, the client cannot complete the chain unless it fetches or caches the intermediate itself, which browsers often do and other clients do not. See Certificate chains and path validation, The TLS 1.3 handshake and Anatomy of an X.509 certificate.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9