What an Nginx Server Block Does
Every website nginx serves is described by a server block: a declaration of which domain names it answers to, which port it listens on, and how each request path is handled. The three patterns in this generator cover the vast majority of real deployments. A static site serves files from disk. A reverse proxy forwards requests to an application (Node, Python, Go) running on localhost. A PHP site hands .php files to PHP-FPM. Choosing the wrong pattern is the root cause of most nginx confusion — so start by naming yours.
Pattern 1: Static Sites
The static pattern is the simplest and fastest: a root directory, an index file, and a try_files directive that serves the exact file or a clean 404. Because nginx serves these files directly from disk with sendfile, static sites handle enormous traffic on tiny servers. Single-page applications need one tweak — try_files $uri $uri/ /index.html — so client-side routes do not 404 on refresh. The generator emits the classic variant; add the SPA fallback if your framework handles routing in the browser.
Static block anatomy
listen 80 binds the HTTP port. server_name lists the domains — always include both the bare domain and www, or redirect one to the other to avoid duplicate content. root points at the directory holding your files; a trailing-slash mistake here is a classic 403 source. The location / block with try_files is what makes nginx a file server rather than a proxy.
Pattern 2: Reverse Proxy
Most modern applications do not let nginx serve anything directly. Instead, your Node, Django, Rails or Go process listens on localhost:3000 (or a Unix socket), and nginx — battle-hardened at accepting internet traffic — forwards each request to it. This split of responsibilities is the standard production architecture: nginx terminates TLS, serves static assets, rate-limits and buffers slow clients, while your app focuses purely on application logic.
Three proxy directives matter most. proxy_pass is the destination — note the trailing-slash semantics: proxy_pass http://localhost:3000/ with a slash rewrites the URI, without it passes the full path through. proxy_set_header Host $host preserves the original hostname, which frameworks need for URL generation and virtual hosting. X-Real-IP and X-Forwarded-For preserve the client address for logging and rate limiting, since your app would otherwise see every request as coming from 127.0.0.1.
Proxy pitfalls
- WebSockets need upgrades: add proxy_http_version 1.1 plus Upgrade and Connection headers, or sockets silently fail.
- Large uploads: the default client_max_body_size is 1MB — uploads bigger than that get a 413 until you raise it.
- Timeouts: long-running endpoints need proxy_read_timeout raised above the default 60s.
- Header size: apps with big cookies or JWTs can trip proxy_buffer_size limits with cryptic 502s.
Pattern 3: PHP via PHP-FPM
PHP does not run inside nginx; it runs in a separate PHP-FPM process pool, and nginx forwards .php requests to it over a Unix socket (or TCP). The location ~ \.php$ block is the handoff: fastcgi_pass points at the socket, and the fastcgi_params include file sets the CGI variables PHP expects. The try_files $uri =404 guard inside the PHP location is a security essential — without it, a crafted request can trick nginx into executing arbitrary uploaded files as PHP.
Match the socket path to your installed PHP version (php8.2-fpm.sock, php8.3-fpm.sock, and so on) — a version mismatch after an OS upgrade is one of the most common causes of sudden 502 Bad Gateway errors on PHP sites. The generator emits the 8.2 path; adjust to taste.
Adding HTTPS the Right Way
The SSL toggle adds a 443 listener with certificate paths. In practice, almost nobody writes these by hand anymore: Certbot's nginx plugin obtains free Let's Encrypt certificates and edits your server blocks automatically, including renewal. The generated snippet shows you what Certbot produces so you can read and verify it — ssl_certificate pointing at the fullchain.pem, ssl_certificate_key at the privkey.pem, plus a redirect from port 80 to 443 that the generator's HTTP block pairs with.
Two HTTPS details worth knowing: always redirect all HTTP traffic to HTTPS (never serve the same content on both), and enable HTTP/2 with a single listen 443 ssl http2 directive — it is a free performance upgrade for modern browsers.
Testing and Reloading Safely
Never reload nginx blind. The ritual is: nginx -t to test the configuration syntax, then systemctl reload nginx (not restart — reload keeps existing connections alive). A failed -t tells you the exact file and line number; fix it before reloading. Keep known-good configs in version control, and for critical sites, test with nginx -T, which dumps the entire effective configuration so you can see exactly what nginx thinks you asked for.
Reading nginx errors
- 502 Bad Gateway: nginx is fine, but the upstream (your app, PHP-FPM) is down or unreachable — check the process and socket.
- 403 Forbidden: permissions on the root directory or a missing index file.
- 404 on every route: try_files misconfigured, or an SPA without the /index.html fallback.
- 413 Payload Too Large: raise client_max_body_size.
- Connection refused on 443: certificate paths wrong or the 443 listener missing.
How This Generator Helps
NginxConf removes the blank-page problem: pick static, reverse proxy or PHP in the rail, enter your domain, toggle SSL, and copy a clean, commented server block — or download it as a .conf file ready to drop into sites-available. Learn the patterns once from the generated output, then customise with confidence.
From Config to Production: A Checklist
A generated server block is the start, not the finish. Work through this before going live: place the file in sites-available and symlink it into sites-enabled; run nginx -t and fix every warning; reload (never restart) so existing connections survive; test the domain with curl -I to confirm the right block answers; verify HTTPS redirects; check the certificate expiry and enable auto-renewal; confirm your app receives the X-Forwarded headers it needs; load-test the proxy timeouts with a slow endpoint; and set up log rotation so access logs do not fill the disk. The generator's tip line covers the core commands — this checklist covers everything around them.
When Not to Use Nginx
Nginx is the right default, but not the only answer. For pure static sites, a CDN or object storage fronted by a CDN is simpler and cheaper than a server you maintain. For containerised apps behind an orchestrator, an ingress controller or service mesh may already terminate TLS. For local development, your framework's dev server is enough — nginx adds nothing until you need production concerns like TLS termination, caching or rate limiting. Choose nginx when you need a robust, boring edge: TLS, static files, buffering and proxying in one battle-tested binary.
Caching Static Assets
Once your server block works, make it fast. Add expires headers for images, CSS and JS so browsers cache them — a location block matching static extensions with expires 30d cuts repeat-visit load dramatically. Enable gzip (or brotli) for text assets — a few lines that typically shrink HTML, CSS and JS by 60–70%. For high-traffic sites, proxy_cache in front of your app can absorb huge spikes. These three — expires, compression, caching — are the highest-leverage performance wins in nginx, and they all live in the same server block you just generated.
Rate Limiting in a Few Lines
Once your proxy serves traffic, protect it. Nginx rate limiting needs two directives: one defines a shared memory zone keyed by client address, and the other applies that zone inside your location block. A modest per-second allowance with a small burst absorbs normal browsing while blunting scrapers and brute-force login attempts. Start permissive and tighten based on what the access logs show — an over-aggressive limit locks out real users behind shared office addresses, which is worse than the bots you were trying to stop.