When implementing a reverse proxy for internal APIs, we often encounter certificate management challenges. The core problem emerges when:
- Internal clients need to connect securely without managing self-signed certificates
- The upstream API server requires SSL connections
- You need application-level authentication before proxying
Here's the optimal solution using Nginx's proxy capabilities:
server {
listen 443 ssl;
server_name api-gateway.example.com;
# Client-facing SSL
ssl_certificate /path/to/public.crt;
ssl_certificate_key /path/to/private.key;
location /validate {
# Rails authentication endpoint
proxy_pass http://rails-app:3000;
proxy_set_header X-Original-URI $request_uri;
}
location /api/ {
# SSL proxy to upstream
proxy_pass https://upstream-api.internal/;
proxy_ssl_certificate /path/to/upstream-client.crt;
proxy_ssl_certificate_key /path/to/upstream-client.key;
proxy_ssl_trusted_certificate /path/to/upstream-ca.crt;
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
# Pass through auth headers
proxy_set_header Authorization $http_authorization;
}
}
Three practical approaches for certificate management:
1. Full SSL Termination
# Simple termination (client to nginx SSL only)
server {
listen 443 ssl;
proxy_pass http://upstream-api.internal;
# ... SSL config for client side only
}
2. SSL Re-encryption
# Full SSL path (client to nginx to upstream)
server {
listen 443 ssl;
proxy_pass https://upstream-api.internal;
proxy_ssl_server_name on;
# ... Both client and upstream SSL config
}
3. Conditional SSL Passthrough
# Hybrid approach based on authentication
map $upstream_http_x_ssl_required $backend_scheme {
"1" "https";
default "http";
}
server {
listen 443 ssl;
if ($http_authorization = "") {
return 401;
}
proxy_pass $backend_scheme://upstream-api.internal;
}
When implementing SSL proxy:
- Enable keepalive connections to upstream:
proxy_http_version 1.1;
- Adjust buffer sizes:
proxy_buffer_size 16k; proxy_buffers 4 32k;
- Consider SSL session reuse:
ssl_session_cache shared:SSL:10m;
Essential Nginx debugging directives:
error_log /var/log/nginx/proxy_error.log debug;
proxy_intercept_errors on;
proxy_next_upstream error timeout invalid_header;
For troubleshooting SSL handshakes:
openssl s_client -connect upstream-api.internal:443 -showcerts -debug
Recommended security practices:
# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:...';
# Proxy security
proxy_hide_header X-Powered-By;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
When implementing a reverse proxy for internal APIs with self-signed certificates, we face a dual SSL challenge:
- Client-to-Nginx SSL termination
- Nginx-to-upstream SSL passthrough
Here's the complete nginx configuration that handles both SSL termination and upstream SSL:
server {
listen 443 ssl;
server_name api-gateway.internal;
# SSL termination for client connections
ssl_certificate /etc/nginx/ssl/public.crt;
ssl_certificate_key /etc/nginx/ssl/private.key;
location / {
# Rails auth via X-Sendfile
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# Upstream SSL configuration
proxy_ssl_certificate /etc/nginx/ssl/upstream_client.crt;
proxy_ssl_certificate_key /etc/nginx/ssl/upstream_client.key;
proxy_ssl_trusted_certificate /etc/nginx/ssl/upstream_ca.crt;
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
proxy_ssl_session_reuse on;
proxy_pass https://upstream-api.internal:8443;
}
}
For environments using self-signed certificates between nginx and upstream servers:
proxy_ssl_trusted_certificate /etc/nginx/ssl/upstream_ca.crt;
proxy_ssl_verify off; # Only for development!
To optimize SSL handshake performance between nginx and upstream:
proxy_ssl_session_reuse on;
proxy_ssl_protocols TLSv1.2 TLSv1.3;
proxy_ssl_ciphers HIGH:!aNULL:!MD5;
keepalive_timeout 75s;
keepalive_requests 100;
Debug upstream SSL problems with these nginx directives:
error_log /var/log/nginx/ssl_error.log debug;
proxy_ssl_server_name on;
proxy_ssl_name $proxy_host;
For scenarios requiring HTTP between client and nginx, but SSL to upstream:
server {
listen 80;
server_name api-gateway.internal;
location / {
proxy_pass https://upstream-api.internal:8443;
proxy_ssl_certificate /etc/nginx/ssl/client.pem;
proxy_ssl_verify off; # For self-signed certs
}
}
Remember to configure appropriate headers and timeouts based on your API requirements.