Skillbase / spm
Packages

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