Common Nginx Production Mistakes: Fix Them Now

Common Nginx Production Mistakes: Fix Them Now

Common Nginx Production Mistakes: Fix Them Now

Common Nginx Production Mistakes: Fix Them Now

Quick Answer: What are the most common mistakes when using nginx in production?

The most common mistakes when running nginx in production cluster around configuration defaults that were never revisited: leaving `client_max_body_size` at 1 MB, keeping `proxy_buffering` on for streaming endpoints, forgetting to forward `Host` and `X-Forwarded-*` headers, and reloading configs without running `nginx -t` first. Most of these live in a handful of directives inside `nginx.conf` and its included site files, so fixes are usually small edits plus a reload rather than a rebuild. (Nginx)

The same mistakes repeat across teams because nginx ships with sane but conservative defaults aimed at small sites, not busy applications. A reverse-proxy setup needs different tuning than a static file server, and treating both the same way causes most incidents.

Before changing anything, check the open-file limits, the proxy header block, the body size limit, the TLS protocol list, the log rotation setup, and whether a default server block exists to catch unknown hostnames.

Nginx powers a staggering share of the web. Independent surveys have repeatedly placed it as the most-used web server among the top million busiest sites, ahead of Apache. That popularity is exactly why its mistakes are so visible: a single bad directive can take down thousands of requests before anyone notices. The good news is that almost every common nginx failure is a known, documented pattern with a short fix. (nginx documentation)

This guide walks through the mistakes that show up again and again in production reviews, why they happen, and what to change. No war stories required, just the directives that matter.

Leaving the Default Body Size Limit in Place

Leaving the Default Body Size Limit in Place

The `client_max_body_size` directive defaults to 1 megabyte. That number is fine for a brochure site and disastrous for anything that accepts uploads.

The symptom is unmistakable: users report 413 Request Entity Too Large errors the moment they try to upload a profile photo, a PDF, or a CSV import. Developers test locally with small files, everything passes, and the bug ships.

The fix is a single line in the `http`, `server`, or `location` block:

Set it to match the largest legitimate upload the application accepts, and enforce the real limit in the app itself. Setting it to `0` disables the check entirely, which is a security problem disguised as a fix.

Forgetting to Forward Proxy Headers

When nginx sits in front of an application server, it becomes the client as far as the app is concerned. Without explicit headers, the app sees every request coming from `127.0.0.1` and builds URLs against the wrong host.

The standard block that fixes this looks like the following, and it belongs in every proxied location:

Skip it and the consequences compound. Password reset emails link to ` Rate limiting in the app treats every user as the same IP. Cookies set without the right scheme get rejected by browsers. Analytics logs become useless.

The `ngx_http_proxy_module` documentation lists the full set of available headers, and it is worth reading once rather than debugging redirect loops later.

Breaking Streaming With Proxy Buffering

Nginx buffers responses by default. For ordinary pages this is a performance win. For server-sent events, long-polling, and streaming APIs, it is a silent killer.

The page loads. The connection stays open. And nothing arrives, because nginx is holding the response until the buffer fills or the upstream closes. Users see a spinner that never resolves.

The fix is to disable buffering for the specific location that streams:

Turning buffering off globally throws away a real performance benefit, so scope it to the streaming endpoints only. This is one of those mistakes where the correct answer is “it depends on the location,” and blanket changes cause new problems.

Reloading Without Testing the Config

Every experienced operator has a story about a typo that took a site down. The difference between a five-second blip and a twenty-minute outage is usually one command:

This tests the configuration for syntax errors and, importantly, for things like missing SSL certificate files. Only after it passes should `nginx -s reload` run. A reload is graceful: old workers finish their requests while new workers pick up the new config. A restart is not, and it should be reserved for cases where reload genuinely fails.

Skipping the test is the classic self-inflicted outage. Automating the test into the deploy pipeline removes the temptation entirely.

Ignoring Open File Limits

Here is a mistake that hides until traffic spikes. Nginx is configured with `worker_connections 1024`, the team assumes it can handle 1024 connections per worker, and then under load the error log fills with “Too many open files.”

The reason is that each connection consumes a file descriptor, and the operating system’s limit per process is often lower than what nginx is configured to use. The fix has two halves: raise the OS limit with `worker_rlimit_nofile` in the config and with `ulimit -n` at the system level, then set `worker_connections` to a value the system can actually support.

A rough formula: maximum clients equals `worker_processes × worker_connections`, but only if the file descriptor limit is at least that high. Many teams double the connection number and forget the other half of the equation.

Running Without a Default Server Block

Nginx routes requests by the `Host` header. If a request arrives with a hostname that matches no `server_name`, nginx falls back to the first server block in the configuration.

That fallback is a quiet security and correctness problem. A scanner hitting the server’s IP address directly gets served whichever site happens to be first. In setups with multiple TLS certificates, the wrong certificate can be presented, triggering browser warnings for legitimate users.

The remedy is an explicit catch-all:

Returning 444 closes the connection without a response, which is the standard way to drop traffic that does not belong to any configured site.

Stale TLS Configuration

TLS settings age faster than almost anything else in an nginx config. A configuration written three years ago may still permit obsolete protocol versions and weak ciphers, and audits flag it every time.

The core directives are `ssl_protocols` and `ssl_ciphers`. Modern configurations restrict protocols to TLS 1.2 and 1.3 and rely on a curated cipher list. Two additions matter just as much: OCSP stapling, which speeds up certificate validation, and HSTS, which tells browsers to refuse plain HTTP for the domain.

Recommended values change as browsers drop support for older standards, so the right move is to review this section on a schedule rather than copy a snippet once and forget it. Treat TLS config as a living document.

Unrotated Logs Filling the Disk

Access logs grow with traffic. On a busy server, an unrotated log file can reach gigabytes in weeks. When the disk fills, nginx cannot write logs, and depending on the setup, the whole server can become unresponsive.

The standard fix is logrotate, usually already installed on Linux systems. A configuration that rotates daily, compresses old files, and keeps a set number of days is enough for most deployments. The critical detail is signaling nginx after rotation so it reopens its file handles, otherwise it keeps writing to the deleted file and the disk never frees up.

Watching disk usage is not glamorous, but this mistake causes a surprising share of production incidents.

Misconfigured Rate Limiting

Rate limiting is one of nginx’s most useful features and one of its easiest to get wrong. Two failure modes dominate.

The first is applying `limit_req` without a properly sized shared memory zone. The `limit_req_zone` directive needs a `zone=` parameter with enough memory to track the keys, and an undersized zone silently drops tracking accuracy. The second is setting the burst and delay parameters so aggressively that legitimate users get 503 errors during normal traffic bursts.

The `ngx_http_limit_req_module` documentation explains the burst and `nodelay` options in detail. The practical rule: start permissive, watch the logs, and tighten based on evidence rather than guesswork.

Skipping Compression and Caching Basics

Two omissions cost performance without producing errors, which is why they survive so long.

Gzip compression is off by default for most content types. Enabling it for text, JSON, CSS, and JavaScript cuts transfer size dramatically. Images and video should be excluded, since they are already compressed.

Static asset caching is similarly neglected. Without `expires` or `Cache-Control` headers on CSS, JavaScript, and fonts, every page load re-downloads them. Adding a long cache lifetime with hashed filenames is a five-minute change with a lasting payoff.

Neither mistake breaks anything. Both quietly tax every visitor.

Permission and User Directive Errors

Nginx is designed to start as root, bind to privileged ports, and then drop to an unprivileged user for worker processes. The `user` directive controls that handoff.

Common errors include running workers as root (unnecessary and risky), pointing the `user` directive at an account that does not exist, or making temp directories world-writable to “fix” a permissions error. The last one is a genuine security hole dressed up as a solution.

The correct approach is to confirm the worker user owns the files it needs, keep temp paths restricted, and never grant broader permissions than the specific error requires. If a permission error appears, read the path in the message before changing anything.

A Quick Comparison of Mistakes and Fixes

Mistake Symptom Fix
Default body size 413 errors on upload Raise `client_max_body_size`
Missing proxy headers Wrong redirects, bad logs Set `Host` and `X-Forwarded-*`
Buffering on streams Spinner that never ends `proxy_buffering off` per location
No config test Outage from a typo Run `nginx -t` before reload
Low file limits “Too many open files” Raise `worker_rlimit_nofile` and `ulimit`
No default server Wrong site on IP hits Add `default_server` catch-all
Stale TLS Audit findings, warnings Review protocols and ciphers
Unrotated logs Full disk Configure logrotate
Bad rate limits Mass 503s or no effect Size the zone, tune burst
No gzip or caching Slow page loads Enable both for static assets

Practical Habits That Prevent Most of These

Configuration review catches errors, but habits prevent them. A few practices pay for themselves quickly.

Test every change with `nginx -t` before reloading, and make that step automatic in deployment scripts. Keep configuration in version control so every change has a diff and a reason. Review the TLS section on a calendar reminder rather than waiting for an audit. Monitor disk usage and error logs, because both announce problems before users do.

For teams that produce a lot of visual assets, screenshot workflows and mockup generation often run through the same nginx instance, which makes body size limits and caching especially relevant. Tools like an AI mockup generator for product design and an AI product photography generator for ecommerce push large files through the proxy, so tuning upload limits and cache headers for those endpoints matters more than for a plain marketing page. The same is true for internal documentation: guides on how to take a screenshot on a Mac or how to take a screenshot on Windows get shared as image-heavy pages that benefit from compression and caching.

Finally, document the why behind each non-default directive. Six months later, nobody remembers why `proxy_buffering off` sits in one location and not another, and the next engineer removes it.

Conclusion

The most common nginx mistakes are not exotic. They are defaults left untouched, headers left unset, and configs reloaded without a test. Each one has a documented fix that takes minutes, and each one has caused real outages that took far longer to diagnose.

The pattern behind all of them is the same: nginx’s defaults assume a small, simple site, and production is neither. Auditing a config against the list above, then building `nginx -t` and version control into the workflow, prevents the majority of incidents before they start. The server is reliable. The configuration is where the work lives.

FAQ

Why does nginx return 413 errors on file uploads?

The `client_max_body_size` directive defaults to 1 MB, so any upload larger than that is rejected before it reaches the application. Raising the limit in the relevant `server` or `location` block resolves it, and the application should still enforce its own maximum.

What is the difference between reloading and restarting nginx?

A reload (`nginx -s reload`) starts new worker processes with the updated configuration while old workers finish in-flight requests, so there is no downtime. A restart stops everything at once. Reload is the right choice for config changes; restart is a fallback when reload fails.

Why do streamed responses arrive all at once instead of progressively?

Proxy buffering is on by default, so nginx collects the upstream response before forwarding it. For server-sent events or streaming APIs, adding `proxy_buffering off` to that specific location restores real-time delivery without giving up buffering elsewhere.

What causes “Too many open files” in the nginx error log?

Each connection uses a file descriptor, and the operating system limit per process is often lower than `worker_connections` implies. Raising both `worker_rlimit_nofile` in the config and the system-level `ulimit -n` brings the two into line.

How should rate limiting be configured without blocking real users?

Size the shared memory zone in `limit_req_zone` to track the expected number of keys, start with a generous burst allowance, and review logs before tightening. Overly strict burst settings produce 503 errors for legitimate traffic during normal peaks.

Is it safe to run nginx worker processes as root?

No. The standard design has the master process start as root, bind to privileged ports, and hand off to an unprivileged user defined by the `user` directive. Running workers as root removes a key layer of protection for no benefit.

on this page