Using Reverse Proxy with Nginx and Apache for Better Security and Caching
Introduction
A reverse proxy sits in front of your app servers and handles incoming requests on their behalf. Done right, it improves security, performance, and resilience while simplifying TLS, caching, and scaling.
How a reverse proxy works
The proxy terminates client connections, applies policies (TLS/headers/rate limits), forwards requests to one or more backends, then returns responses to the client. It can also cache content and balance load across instances.
Key benefits
- Shield origin: backends are not exposed to the public Internet.
- Load balancing: distribute traffic across multiple servers.
- Caching: serve hot assets from the proxy to cut latency and origin load.
- TLS offload: centralize certificates and enable HTTP/2/3 at the edge.
- Policy control: add security headers, WAF, rate limiting, IP allow/deny lists.
Nginx quick start (reverse proxy + SSL + cache)
# /etc/nginx/conf.d/app.conf
upstream app_backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
# server 10.0.0.12:3000;
# server 10.0.0.13:3000;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:100m
inactive=60m max_size=2g;
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/example.crt;
ssl_certificate_key /etc/ssl/example.key;
# Security headers at the edge
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self' https:" always;
# Static caching (tune by path or mime)
location ~* \.(css|js|png|jpg|jpeg|gif|svg|woff2?)$ {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache STATIC;
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
add_header X-Cache $upstream_cache_status;
}
# Default proxy
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
client_max_body_size 25m; # uploads
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri; # HSTS after validating TLS
}
Tip: For APIs that must not be cached, set Cache-Control: no-store at the app or add a proxy_no_cache rule.
Apache (httpd) quick start (mod_proxy + mod_cache)
# Enable modules (Debian/Ubuntu examples)
# a2enmod proxy proxy_http headers ssl http2 cache cache_disk
<VirtualHost *:443>
ServerName example.com
Protocols h2 http/1.1
SSLEngine on
SSLCertificateFile /etc/ssl/example.crt
SSLCertificateKeyFile /etc/ssl/example.key
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self' https:"
# Forward to backend
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# Disk cache for static
CacheQuickHandler On
CacheRoot "/var/cache/apache2/mod_cache_disk"
CacheEnable disk "/"
CacheIgnoreHeaders Set-Cookie
CacheDefaultExpire 600
ExpiresActive On
ExpiresByType text/css "access plus 10 minutes"
ExpiresByType application/javascript "access plus 10 minutes"
ExpiresByType image/* "access plus 30 minutes"
# Limit request size
LimitRequestBody 26214400 # 25 MB
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>
Hardening checklist
| Item | Why |
|---|---|
| Hide origins (no public access) | Reduce attack surface; only proxy is exposed. |