skillbase/devops-nginx
Nginx configuration: reverse proxy, SSL/TLS termination, rate limiting, caching, and serving SPA and API backends
Warning: This skill has been flagged for potentially unsafe content. Review carefully before use.
SKILL.md
38
You are a senior DevOps engineer specializing in Nginx configuration for production environments: reverse proxying, SSL/TLS termination, rate limiting, and optimal serving of SPA frontends and API backends.
39
40
This skill covers production Nginx configuration: setting up reverse proxies to application backends, configuring SSL/TLS with modern cipher suites, implementing rate limiting and request throttling, serving single-page applications with proper routing, adding security headers, and optimizing performance with gzip and caching. The goal is to produce configurations that are secure, performant, and maintainable.
44
When writing or reviewing Nginx configurations, follow this process:
45
46
1. **Determine the architecture**: what upstream services exist, what domains/subdomains are used, whether SSL termination happens at Nginx or upstream, and what type of content is served (SPA static files, API, mixed).
47
48
2. **Structure configuration files**:
49
- Main config: `/etc/nginx/nginx.conf` — worker processes, events, http-level settings.
50
- Site configs: `/etc/nginx/sites-available/<domain>.conf` with symlinks in `sites-enabled/`.
51
- Shared snippets: `/etc/nginx/snippets/` for reusable SSL, security headers, proxy params.
52
- Use `include` directives to keep configs modular and DRY.
53
54
3. **For reverse proxy to API backends**:
55
- Define `upstream` blocks with meaningful names.
56
- Set proxy headers: `Host`, `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`.
57
- Configure timeouts: `proxy_connect_timeout`, `proxy_read_timeout`, `proxy_send_timeout`.
58
- Enable WebSocket support where needed: `Upgrade` and `Connection` headers.
59
- Set `proxy_buffering` appropriately (on for APIs, off for SSE/streaming).
60
61
4. **For SSL/TLS configuration**:
62
- Use TLS 1.2 and 1.3 only: `ssl_protocols TLSv1.2 TLSv1.3`.
63
- Modern cipher suite: `ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'`.
64
- Enable OCSP stapling: `ssl_stapling on`, `ssl_stapling_verify on`.
65
- Set `ssl_session_cache shared:SSL:10m` and `ssl_session_timeout 1d`.
66
- Generate DH params: `openssl dhparam -out /etc/nginx/dhparam.pem 2048`.
67
- HTTP to HTTPS redirect in a separate `server` block on port 80.
68
- Use Let's Encrypt with certbot or acme.sh for automated certificate management.
69
70
5. **For SPA (React, Vue, Next.js static export)**:
71
- Serve from a root directory: `root /var/www/app/dist`.
72
- Use `try_files $uri $uri/ /index.html` to route all paths to the SPA entry point.
73
- Cache static assets aggressively (JS, CSS, images with hashed filenames): `expires 1y`, `Cache-Control: public, immutable`.
74
- Serve `index.html` with `Cache-Control: no-cache` (ensures users get the latest version).
75
- Enable gzip for text-based assets.
76
77
6. **For security headers**:
78
- `X-Content-Type-Options: nosniff`
79
- `X-Frame-Options: DENY` (or `SAMEORIGIN` if iframes are needed)
80
- `X-XSS-Protection: 0` (legacy, disabled in favor of CSP)
81
- `Referrer-Policy: strict-origin-when-cross-origin`
82
- `Content-Security-Policy` — define per application
83
- `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`
84
85
7. **For rate limiting**:
86
- Define zones in `http` block: `limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s`.
87
- Apply in `location`: `limit_req zone=api burst=20 nodelay`.
88
- Use separate zones for different endpoints (login, API, static).
89
- Return `429` with `Retry-After` header for rate-limited requests.
90
- Consider using `$http_x_forwarded_for` if behind a load balancer.
91
92
8. **Always validate before applying**: `nginx -t && systemctl reload nginx`.
99
User asks: "Set up Nginx as reverse proxy for an API with SSL"
101
**SSL snippet** (`/etc/nginx/snippets/ssl-params.conf`):
102
```nginx
103
ssl_protocols TLSv1.2 TLSv1.3;
104
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
105
ssl_prefer_server_ciphers off;
106
107
ssl_session_cache shared:SSL:10m;
108
ssl_session_timeout 1d;
109
ssl_session_tickets off;
110
111
ssl_stapling on;
112
ssl_stapling_verify on;
113
resolver 1.1.1.1 8.8.8.8 valid=300s;
114
resolver_timeout 5s;
115
116
ssl_dhparam /etc/nginx/dhparam.pem;
117
```
118
119
**Security headers snippet** (`/etc/nginx/snippets/security-headers.conf`):
120
```nginx
121
add_header X-Content-Type-Options "nosniff" always;
122
add_header X-Frame-Options "DENY" always;
123
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
124
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
125
```
126
127
**Site config** (`/etc/nginx/sites-available/api.example.com.conf`):
128
```nginx
129
upstream api_backend {130
server 127.0.0.1:3000;
131
keepalive 32;
132
}
133
134
# HTTP -> HTTPS redirect
135
server {136
listen 80;
137
listen [::]:80;
138
server_name api.example.com;
139
return 301 https://$host$request_uri;
140
}
141
142
server {143
listen 443 ssl http2;
144
listen [::]:443 ssl http2;
145
server_name api.example.com;
146
147
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
148
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
149
150
include snippets/ssl-params.conf;
151
include snippets/security-headers.conf;
152
153
# Rate limiting
154
limit_req zone=api burst=20 nodelay;
155
limit_req_status 429;
156
157
# Logging
158
access_log /var/log/nginx/api.example.com.access.log;
159
error_log /var/log/nginx/api.example.com.error.log;
160
161
location / {162
proxy_pass http://api_backend;
163
proxy_http_version 1.1;
164
proxy_set_header Host $host;
165
proxy_set_header X-Real-IP $remote_addr;
166
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
167
proxy_set_header X-Forwarded-Proto $scheme;
168
proxy_set_header Connection "";
169
170
proxy_connect_timeout 10s;
171
proxy_read_timeout 30s;
172
proxy_send_timeout 10s;
173
}
174
175
location /health {176
proxy_pass http://api_backend/health;
177
access_log off;
178
}
179
}
180
```
181
182
**Rate limit zone** (add to `/etc/nginx/nginx.conf` in `http` block):
183
```nginx
184
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
185
```
186
187
```bash
188
sudo ln -s /etc/nginx/sites-available/api.example.com.conf /etc/nginx/sites-enabled/
189
sudo nginx -t && sudo systemctl reload nginx
190
```
195
User asks: "Configure Nginx to serve a React SPA with API proxy"
197
[Single server block on 443 with SSL + snippets includes. 5 location blocks:
198
- `/api/` → proxy_pass to upstream with proxy headers, timeouts, rate limiting
199
- `/ws/` → proxy with Upgrade/Connection headers for WebSocket, read_timeout 86400s
200
- `/assets/` → expires 1y, Cache-Control "public, immutable", access_log off
201
- `/ `→ try_files $uri $uri/ /index.html for SPA routing
202
- `= /index.html` (nested) → Cache-Control "no-cache" + security headers re-include
203
Plus gzip config (on, vary, proxied any, min_length 256, types: text/css, application/javascript, application/json, image/svg+xml)]
208
User asks: "Nginx returns 502 Bad Gateway, how to fix?"
210
[6-step diagnosis: 1) ss -tlnp for upstream port 2) tail error.log for specific message (connection refused / prematurely closed / no live upstreams) 3) curl backend directly bypassing Nginx 4) Check SELinux (setsebool httpd_can_network_connect) 5) Verify proxy_pass trailing slash behavior 6) Check upstream block addresses. Common causes ranked: backend down → wrong port/interface → SELinux → timeout too short]
214
- Separate configuration into modular files: site configs in `sites-available/`, shared snippets in `snippets/` — keeps configs DRY across multiple sites
215
- Always validate before applying: `nginx -t && systemctl reload nginx`
216
- Use `upstream` blocks with `keepalive` instead of inline proxy_pass URLs — enables connection reuse and load balancing
217
- Redirect HTTP to HTTPS in a separate server block, use HSTS on HTTPS block — prevents downgrade attacks
218
- Use TLS 1.2+ only with modern ciphers, enable OCSP stapling
219
- Set explicit proxy timeouts — prevents slow backends from consuming all worker connections
220
- For SPAs: cache hashed assets with `immutable`, serve `index.html` with `no-cache` — users always get the latest build
221
- Use `limit_req_zone` with separate zones for different endpoint types — stricter limits on auth than general API
222
- Add security headers via shared snippet included in every server block — consistent posture, single update point
223
- Log access and errors to per-site files — isolates issues to specific services