Nginx

Nginx - Medium

Page 2 of 2

Gzip and Basic Caching

Compressing responses before they leave Nginx cuts bandwidth and speeds up page loads, especially for text-heavy responses like JSON and HTML.

gzip on;
gzip_types text/plain application/json text/css application/javascript;
gzip_min_length 256;   # don't bother compressing tiny responses

Note what's missing from gzip_types: image formats. Files like JPEGs and PNGs are already compressed, so running gzip on them again wastes CPU for zero benefit and sometimes even makes them larger. Beyond gzip, Nginx can also cache responses from upstream backends, storing a copy on disk so repeated requests for the same content don't have to hit the backend at all.

http {
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=1g;

    server {
        location / {
            proxy_pass http://backend_app;
            proxy_cache app_cache;
            proxy_cache_valid 200 10m;   # cache successful responses for 10 minutes
        }
    }
}

proxy_cache_path sets aside a directory on disk plus a named shared memory zone (app_cache, sized 10m) that tracks which keys are cached and where. proxy_cache_valid controls how long a given response status stays fresh before Nginx goes back to the backend to refresh it. This is a deeper topic on its own, with real subtlety around cache keys and invalidation, but the core idea is worth filing away: if a backend is getting hammered with identical repeated requests, caching at the proxy layer is often a far cheaper fix than scaling the backend itself.


Timeouts

A handful of timeout directives control how patient Nginx is with the backend, and getting them wrong causes two very different failure modes.

location / {
    proxy_pass http://backend_app;

    proxy_connect_timeout 5s;   # how long to wait to establish a connection to the backend
    proxy_read_timeout 60s;     # how long to wait for the backend to send data once connected
}

Set proxy_read_timeout too low, and Nginx will cut off a backend that's simply doing legitimately slow work (a big report export, a heavy database query), returning a premature 504 Gateway Timeout to the client even though the backend would have finished successfully. Set it too high, and a genuinely stuck or overloaded backend will hold that connection (and the worker connection slot behind it) open far longer than it should, which under real load can chew through your available connections and take down requests that had nothing to do with the original slow one. There's no universal correct value, it depends on what your slowest legitimate request actually needs, but it should always be a deliberate choice, not whatever the default happened to be.


Troubleshooting Workflow

When something's wrong, the error log is where you start:

opsquiz@devops-essentials
opsquiz@devops:~$ tail -f /var/log/nginx/error.log
2026/09/04 10:22:11 [error] 1104#1104: *55 connect() failed (111: Connection refused) while connecting to upstream, client: 203.0.113.7, server: app.example.com, upstream: "http://10.0.1.10:3000/"

connect() failed with Connection refused means the backend port simply isn't accepting connections, the process is likely down or never started. upstream timed out means the backend accepted the connection but never responded in time, pointing at proxy_read_timeout or a genuinely stuck backend rather than a dead one. Both messages name the exact upstream address involved, which is your starting point for checking whether that specific backend instance is healthy.

The other essential troubleshooting command is nginx -T, which dumps the entire merged configuration, every included file, flattened into one output, exactly as Nginx sees it after processing all your include statements.

sudo nginx -T | less

This matters because real configs are split across many files, and it's easy to have a location / block in one file silently shadowed or overridden by something in another. -T shows you the config Nginx is actually running, not the config you think you wrote across a dozen separate files. It's especially useful after someone else changed something and you're not sure what: nginx -T | grep -A5 proxy_pass will show you exactly what every location block in the running config is actually forwarding to, without you having to go hunting through sites-enabled, conf.d, and whatever else got included along the way.

One more habit worth building here: when a symptom shows up only for certain requests, it's worth checking $upstream_response_time and $request_time if your access log format includes them. The first tells you how long the backend took to respond, the second tells you the total time including everything Nginx itself did. A big gap between the two, rather than the backend simply being slow, points you toward Nginx-side causes like buffering or a saturated worker instead of blaming the backend by default.

    Welcome to OpsQuiz!

    Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.