When implementing SSL/TLS with Nginx reverse proxy, you need to consider two distinct certificate scenarios:
- Frontend certificates: Used between client browsers and Nginx
- Backend certificates: Used between Nginx and upstream servers
For your domain-facing Nginx server (x.x.x.x), you should install the actual domain certificates (example1.com, example2.com, etc.). This ensures clients see valid certificates for the domains they're accessing.
server {
listen 443 ssl;
server_name example1.com;
ssl_certificate /etc/nginx/ssl/example1.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example1.com/privkey.pem;
location / {
proxy_pass https://a.b.c.d:1234;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ssl/upstream_ca.pem;
proxy_ssl_name example1.com;
}
}
For upstream servers (a.b.c.d:1234), you have several valid approaches:
- Option 1: Use the same domain certificate (not recommended for security)
- Option 2: Generate new certificates signed by internal CA
- Option 3: Use self-signed certificates with proper validation
The most secure approach is to create an internal CA and issue separate certificates for backend servers:
# On upstream server a.b.c.d:1234 (example1-backend)
ssl_certificate /etc/ssl/certs/example1-backend.crt;
ssl_certificate_key /etc/ssl/private/example1-backend.key;
Then configure Nginx to verify these certificates:
proxy_ssl_certificate /etc/nginx/ssl/client.crt;
proxy_ssl_certificate_key /etc/nginx/ssl/client.key;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
proxy_ssl_verify_depth 2;
For production environments, implement these security measures:
- Use separate certificates for frontend and backend
- Enable OCSP stapling on frontend
- Implement certificate pinning for critical services
- Rotate certificates regularly (automate with ACME)
- Monitor certificate expiration dates
Here's a full working example for example1.com:
# Frontend configuration
server {
listen 443 ssl http2;
server_name example1.com;
# Public certificate
ssl_certificate /etc/letsencrypt/live/example1.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example1.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass https://a.b.c.d:1234;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Upstream SSL verification
proxy_ssl_certificate /etc/nginx/ssl/client.crt;
proxy_ssl_certificate_key /etc/nginx/ssl/client.key;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ssl/internal-ca.crt;
proxy_ssl_verify_depth 2;
proxy_ssl_session_reuse on;
}
}
If encountering SSL errors between Nginx and upstream:
- Check time synchronization (certificates are time-sensitive)
- Verify the CA chain is complete
- Ensure the upstream server's certificate includes proper SANs
- Inspect Nginx error logs at /var/log/nginx/error.log
- Test with openssl s_client -connect a.b.c.d:1234 -showcerts
When configuring SSL certificates in an Nginx reverse proxy setup, we need to distinguish between two distinct TLS termination points:
- Client-facing termination: Between end users and your proxy server
- Upstream termination: Between your proxy and backend servers
For your frontend configuration (client ↔ nginx), you'll need:
server {
listen 443 ssl;
server_name example1.com;
ssl_certificate /etc/nginx/ssl/example1.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example1.com/privkey.pem;
location / {
proxy_pass https://a.b.c.d:1234;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ssl/ca-certs.pem;
}
}
The client-facing certificate must match the domain name users access (example1.com). This is non-negotiable for browser trust.
For the upstream connection (nginx ↔ backend), you have three viable approaches:
Option 1: Reuse Domain Certificates (Simplest)
# On backend server a.b.c.d:1234
ssl_certificate /path/to/example1.com.crt;
ssl_certificate_key /path/to/example1.com.key;
This works when your proxy can resolve the backend's certificate CN/SANs.
Option 2: Internal CA-Signed Certificates (More Secure)
# Generate for each backend
openssl req -newkey rsa:2048 -nodes -keyout backend1.key \
-subj "/CN=backend1.internal" -out backend1.csr
# Sign with internal CA
openssl x509 -req -in backend1.csr -CA internal-ca.crt \
-CAkey internal-ca.key -CAcreateserial -out backend1.crt
Configure Nginx to trust your internal CA:
proxy_ssl_trusted_certificate /etc/nginx/ssl/internal-ca.crt;
proxy_ssl_verify on;
Option 3: Self-Signed with Certificate Pinning
# Generate self-signed
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout backend.key -out backend.crt \
-subj "/CN=backend1.internal" \
-addext "subjectAltName=DNS:backend1.internal"
# Configure Nginx
proxy_ssl_certificate /etc/nginx/ssl/backend1.crt;
proxy_ssl_certificate_key /etc/nginx/ssl/backend1.key;
proxy_ssl_verify off; # We're pinning instead
proxy_ssl_verify_depth 2;
proxy_ssl_server_name on;
Here's a complete setup using internal CA certificates:
# nginx.conf fragment
upstream backend_example1 {
server a.b.c.d:1234;
keepalive 16;
}
server {
listen 443 ssl http2;
server_name example1.com;
# Client-facing cert
ssl_certificate /etc/letsencrypt/live/example1.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example1.com/privkey.pem;
# SSL optimizations
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
location / {
proxy_pass https://backend_example1;
# Upstream SSL config
proxy_ssl_certificate /etc/nginx/ssl/client.crt;
proxy_ssl_certificate_key /etc/nginx/ssl/client.key;
proxy_ssl_trusted_certificate /etc/nginx/ssl/internal-ca.crt;
proxy_ssl_verify on;
proxy_ssl_protocols TLSv1.2 TLSv1.3;
# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
- Use separate certificates for frontend and backend communication
- Automate renewals with tools like certbot for client-facing certs
- Maintain a CRL (Certificate Revocation List) for internal certificates
- Set appropriate certificate lifetimes (90 days for public, 1 year for internal)
- Implement OCSP stapling for client-facing certificates