keepcalculator

06 Numbers and Powers

Secure Nginx TLS Behind an Edge Proxy

Card No. 06·4793 · Updated · Standard formula, shown below

This guide shows you how to configure Nginx to use TLS when it sits behind an edge proxy (like HAProxy, AWS ALB, or Traefik). You’ll learn the correct certificate setup, proxy headers, and cipher choices so that end-to-end encryption works without breaking redirects or client IP logging. You’ll need root access to the Nginx server and a valid certificate (or a self-signed one for testing).

What You’ll Need

  • A server running Nginx (version 1.18 or later recommended)
  • Root or sudo access to edit Nginx configuration files
  • A TLS certificate and private key (e.g., from Let’s Encrypt, or self-signed for testing)
  • Knowledge of your edge proxy’s IP address or hostname
  • Basic familiarity with the command line and Nginx configuration syntax

Step-by-Step

  1. Decide on TLS termination point. You have two main options: terminate TLS at the edge proxy and forward plain HTTP to Nginx, or pass the TLS connection through to Nginx (SSL passthrough). This guide covers the first, most common scenario—where the edge proxy handles the client-facing TLS and Nginx still uses TLS for the internal connection. This is often called “TLS between proxy and backend.�
  2. Generate or obtain a certificate for the internal connection. If you don’t have a certificate that the edge proxy trusts, you can create a self-signed one for testing. For production, use a certificate from a trusted CA, or use the same certificate that the edge proxy uses (if it covers the internal hostname). To generate a self-signed certificate, run:
    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/backend.key -out /etc/nginx/ssl/backend.crt

    Make sure the /etc/nginx/ssl directory exists first.

  3. Configure Nginx to listen on HTTPS. Edit your site’s server block (usually in /etc/nginx/sites-available/). Change the listen directive to use SSL, and point to your certificate and key:
    server {
        listen 443 ssl;
        server_name backend.internal;
    
        ssl_certificate /etc/nginx/ssl/backend.crt;
        ssl_certificate_key /etc/nginx/ssl/backend.key;
    
        # Rest of your configuration...
    }

    If you’re using a certificate from a CA, also set ssl_trusted_certificate to the CA bundle if needed.

  4. Set secure SSL protocols and ciphers. Add these lines inside the server block (or in the http block for global use):
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;

    This disables older, insecure protocols and prefers strong ciphers.

  5. Forward the original protocol and host headers. Since the edge proxy terminates the client-facing TLS, Nginx needs to know the original scheme (http or https) to generate correct redirects. Add these proxy headers in your location blocks or in the server block:
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    If you’re using Nginx as a reverse proxy to another backend, add these to the proxy_pass section.

  6. Handle redirects correctly. If your application tries to redirect to an HTTP URL, you may want to force HTTPS. You can add a header to tell the client that the connection is secure:
    add_header X-Forwarded-Proto $scheme;

    Or, if you need to rewrite redirects, use the proxy_redirect directive. For example, if your backend sends a redirect to http://backend.internal, you can rewrite it to https://$host:

    proxy_redirect http://backend.internal /;

    This is often necessary when the backend doesn’t know it’s behind TLS.

  7. Test the configuration. Run sudo nginx -t to check for syntax errors. If it passes, reload Nginx with sudo systemctl reload nginx (or sudo service nginx reload).
  8. Verify the connection. From the edge proxy, test the connection to Nginx using curl with the internal hostname and your certificate:
    curl -v https://backend.internal --resolve backend.internal:443:127.0.0.1

    You should see the TLS handshake succeed and the certificate details. Also check that the X-Forwarded-Proto header is being set correctly by looking at your application logs or using a test endpoint.

Common Mistakes

  • Forgetting to set X-Forwarded-Proto. Without it, your application may generate HTTP links or redirects, breaking the secure connection.
  • Using a certificate that doesn’t match the hostname. The edge proxy will reject the connection if the certificate’s CN or SAN doesn’t match the hostname you use to connect to Nginx.
  • Leaving SSL protocols too permissive. Allowing TLSv1.0 or TLSv1.1 weakens security and may cause warnings in modern browsers.
  • Not restarting Nginx after changes. Always run nginx -t and reload, otherwise your changes won’t take effect.
  • Mixing up the certificate and key paths. Double-check that the paths are correct and that Nginx has read permissions on them.

Tips

  • Use a dedicated internal hostname (like backend.internal) that is only resolvable inside your network. This makes it easier to manage certificates.
  • If you’re using Let’s Encrypt, you can generate a certificate that covers both the public and internal hostnames, but make sure the internal hostname is in the SAN list.
  • Consider using a certificate authority that supports internal names, or use a private CA if you have many internal services.
  • Enable OCSP stapling if your certificate supports it, to improve performance and privacy:
  • ssl_stapling on;
    ssl_stapling_verify on;
  • Keep your Nginx version up to date to benefit from the latest security patches and TLS features.

Troubleshooting

  • Connection refused or timeout. Check that Nginx is listening on port 443 with sudo netstat -tulpn | grep 443. Also check firewall rules on the Nginx server—make sure port 443 is open to the edge proxy’s IP.
  • Certificate verification fails. If the edge proxy rejects the certificate, check that the certificate file includes the full chain (if using a CA). For self-signed certs, you may need to add the CA to the proxy’s trust store. Also verify the hostname matches the certificate’s SAN.
  • Redirects go to HTTP. This usually means X-Forwarded-Proto is not being set. Check your proxy headers and ensure the application reads that header. You can test by curling with -H "X-Forwarded-Proto: https".
  • SSL handshake errors. Run openssl s_client -connect backend.internal:443 to see the handshake details. Common issues include mismatched ciphers or protocols. Make sure your ssl_protocols and ssl_ciphers are compatible with the edge proxy.
  • Client IP shows the proxy’s IP. If you need the real client IP, you must set X-Forwarded-For and also configure Nginx to use it. Add set_real_ip_from and real_ip_header directives:
  • set_real_ip_from 10.0.0.0/8;  # Replace with your proxy's IP range
    real_ip_header X-Forwarded-For;

Frequently Asked Questions

Do I need TLS between the edge proxy and Nginx if they are on the same private network?

It’s strongly recommended. Even on a private network, traffic can be intercepted if the network is compromised. TLS ensures confidentiality and integrity of data in transit.

Can I use the same certificate for both the edge proxy and Nginx?

Yes, if the certificate’s subject alternative names (SANs) include both the public hostname (for the edge proxy) and the internal hostname (for Nginx). Otherwise, you’ll need separate certificates.

What is the difference between SSL passthrough and TLS termination at the proxy?

With passthrough, the edge proxy forwards the encrypted TLS connection directly to Nginx, so Nginx must handle the client-facing certificate. With termination, the proxy decrypts and re-encrypts, so Nginx sees a different TLS connection (often with a different certificate).

How do I force all HTTP traffic to HTTPS when behind a proxy?

In your Nginx server block, add a redirect from port 80 to 443, but make sure to use the X-Forwarded-Proto header to avoid redirect loops. For example: if ($http_x_forwarded_proto = "http") { return 301 https://$host$request_uri; }

Is it safe to disable SSLv3 and TLSv1.0?

Yes, these protocols have known vulnerabilities (POODLE, BEAST). Modern clients support TLSv1.2 and TLSv1.3, so disabling older versions is a security best practice.

What if my edge proxy doesn’t support setting X-Forwarded-Proto?

Most modern proxies do. If yours doesn’t, you can hardcode the scheme in Nginx by setting proxy_set_header X-Forwarded-Proto https; but be aware that this will always report HTTPS even if the client used HTTP (which may not be a problem if the proxy only accepts HTTPS).

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →