Nginx setup: basic configuration and optimization

The default web server configuration starts dropping requests exactly when the load is at its peak. We configure nginx end to end: we review the current parameters, rewrite the configuration for your load profile, and hand over procedures to your team. Response time evens out, and failures under peak load disappear.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Configuring nginx: what breaks with the default configuration

The default configuration of a web server is designed for one site, static content, and a couple of concurrent visitors — the first-launch scenario.

Local test runs and a developer opening a page alone can sustain it, but it knows nothing about the real load profile: neither about peaks, nor about slow application responses, nor about drops on mobile networks.

The main bottleneck under load is the absence of limits and queues. The number of concurrent connections per process is set formally and not tailored to the load profile, buffers for request headers and bodies are set at random, and application wait timeouts are stretched.

While the application responds instantly, this goes unnoticed; as soon as proxying requests to the application hits a slow handler or external integration, connections pile up, processes exhaust available descriptors — and instead of a response, the client gets an error.

A separate part of the problem is caching and compression. Static content and immutable assets are served anew on every request, caching headers are not set, so all the work falls on both disk and application.

At peak traffic, this produces an avalanche of server errors exactly when the main flow of visitors arrives.

Diagnostics are complicated by the fact that logs show the effect, not the cause: the application looks healthy, while failures are born in the settings for connections, timeouts, and buffers. Let's break down how this is resolved and in what order nginx configuration changes.

What changes after a verified web server configuration

A verified configuration first removes static serving from the application.

Enabled sendfile and tcp_nopush send the file without copying it extra times between buffers, open_file_cache eliminates repeated disk accesses, and try_files handles requests to nonexistent paths directly on the web server.

Static content starts being served many times faster, and the application handles only dynamic content, not serving images and fonts.

Response compression is configured by content type, not “enabled for everything”: pre-compressed file versions are served without CPU load, while for the rest a level is selected at which the gain in size is still justified.

Text resources go to the client at 3-4 times smaller size — the page starts rendering noticeably earlier, and the channel stops being a bottleneck at peaks.

Queues and limits are what protect the backend from collapse. limit_req and limit_conn with burst and nodelay cut off the surge at the entrance: excess requests either wait in a queue or receive an honest rejection instead of finishing off the application.

Connections to the application server are reused via keepalive, so traffic growth does not turn into an avalanche of new connections.

At the fault-tolerance level, max_fails and fail_timeout remove an unresponsive node from rotation and return it to operation automatically, while configuration reload happens without dropping active connections.

The ceiling is set by worker_connections and system descriptor limits. The result: the failure rate during peaks drops to zero, and recovery after a failure takes seconds instead of minutes of downtime.

Three approaches to nginx configuration: manual edits, templates, code

The three modes differ not in editing convenience, but in who is responsible for configuration consistency across a fleet of servers.

With point edits, server and location are changed directly on the node, and a couple of months later no one can say how the staging environment differs from production.

A parameterized template already defines a unified structure and substitutes environment values, but the actual files on disks still live their own lives.

A full-fledged configuration as code makes the repository the source of truth: changes go through review, configuration versioning records who changed a directive and why, and automated deployment delivers it to all nodes identically.

Option When it fits Limitations
Manual configuration edits One server, one-off experiments, debugging behavioral rules Node state diverges, rollback is manual, and when scaling, errors are copied along with the file
Parameterized template Identical topology across several environments, infrequent releases Values are still entered by hand; there is no change history, and divergence is detected only during a failure
Configuration as code A fleet of nodes, frequent changes, requirements for reproducibility and audit A repository, test run, and well-thought-out order of application are needed; otherwise automation spreads the error to all nodes

The transition usually proceeds incrementally: first, edits are moved into a template and duplication between environments is removed, then roles and variables are described, and only after that is auto-application enabled with syntax checking before deployment.

This order makes it possible not to stop the service at every step and leaves the ability to roll back to the previous configuration version.

Work stages: from auditing the current configuration to handing over policies

The order of steps is fixed, because every change is first verified and only then reaches the production server. This way the configuration stops being a set of “situational” edits and becomes a reproducible artifact with history.

  1. Server configuration audit. We capture the current files, enabled modules, and parameter inheritance, and compare them with access and error logs. The output is a list of bottlenecks with impact assessment: where static caching is lost, where timeouts are too broad, where edits were made manually and did not make it into the repository.
  2. Traffic processing scheme. We document what stands in front of and behind the server: load balancer, external cache, applications, and on which node encryption is terminated. The scheme shows which headers need to be passed through and where the real client address is lost.
  3. Configuration draft. We assemble the structure from separate files per domain and service, and move common parameters into a single included file. Caching, request size limits, and redirects are described explicitly, without inheritance “from memory.”
  4. Testing on a staging environment. We bring up a copy of the environment, run the configuration through a syntax check, then with real traffic: redirects, static content, requests to the application. The staging environment catches what is not visible in the file text.
  5. Zero-downtime deployment. We apply reload while preserving connections: new worker processes start, old ones finish current requests. At the moment of switching, not a single request is lost; rollback means returning the previous file version.
  6. Measurements under load. We compare metrics before and after: response time for static and dynamic content, share of requests from cache, number of connections per process. The numbers show whether further fine-tuning is needed.
  7. Handover of policies. Configurations live in a version control system, edits go through staging, and the author and reason remain in history. The on-call engineer receives a short instruction: how to check status, how to roll back, whom to call.

A skipped step surfaces later: a configuration without staging breaks the production service at deployment time, and changes without a policy cannot be reproduced during failure analysis.

What you keep: configuration files, schemes, and policies

Along with a working configuration, you receive a set of artifacts by which it can be maintained and developed without the author's involvement.

This is not an export of settings from the server, but an explained logic: what is responsible for what, why exactly this value was chosen, and what will break if it is changed.

  • Configuration files with comments — every block is labeled: purpose, dependency on other directives, safe value boundaries. An engineer who opens the file six months later will not have to guess why a particular parameter is set.
  • Request processing scheme — the path from client to application: where the connection is accepted, where encryption is terminated, how load is distributed between application servers, and which responses are served directly.
  • Caching policies — what is cached, for how long, by which headers responses are considered stale, and how the cache is purged when content is updated.
  • Access policies — address restrictions, request rate limits, closed service paths, rules for internal and external clients.
  • Certificate management — renewal deadlines, replacement procedure, update automation, and verification that the new certificate was picked up without downtime.
  • Post-edit verification checklist — syntax check before applying, test run, monitoring of logs and response codes, and a procedure for rolling back to the previous version within a minute.
  • Server documentation — directory structure, log locations, who has access, and a change log with the reason for each edit.

The checklist closes the main operational risk: a change applied without verification takes the entire site down. The artifacts remain in your repository — they can be handed over to another engineer or contractor without reconstructing the logic from memory.

Case study: a high-traffic portal — failures and response time

An online store with regular sales: outside peaks, the system ran smoothly, but in the first minutes of a promotion and during evening hours some requests ended in error, and response time in the catalog and checkout increased several times over.

The failure class was always the same — “server did not respond”: the buyer saw an empty page instead of a product card and went to competitors. Expanding the machine fleet helped for a few days, after which the picture repeated.

The cause turned out to be the queue at the entrance. The front-end Nginx held connections to the application synchronously: long requests — report exports, catalog search — occupied all available slots, new connections piled up in the queue and timed out.

Static content was served by the application itself, so every image took resources away from order processing.

We rewrote the virtual host configuration, separating static and dynamic content: images, styles, and scripts are served by the proxy with long cache headers, application responses are buffered, and connections to the backend are kept persistent.

Long requests were moved to a separate route with queue limiting, and connection limits were set so that a failure comes quickly and with a clear code, rather than hanging for minutes.

What changed: the “server did not respond” failure class disappeared completely, availability remains steady during the sale peak, and 95th percentile response time dropped from 3-4 seconds to 0.4-0.6.

The same machine fleet handles peak load, while power headroom tripled — the next promotion, larger than the previous one, was held without purchasing servers and without standing by “just in case.”

How does a reverse proxy differ from a load balancer?

The difference is in roles: a reverse proxy is a server's position in the scheme, while a load balancer is one of the functions that this position can perform.

The proxy stands at the entrance, accepts the connection from the client, terminates TLS, serves static content and cache, and then passes the request to the backend.

While there is one backend, there is no distribution — only a single entry point that hides the internal topology from the client.

When several applications live behind one entrance, the proxy routes requests by domain or path: the admin panel, services, and storefront each get their own backend behind a single external address.

This is still proxying — the task is to separate flows to different applications, not to split one flow between copies.

Distribution of requests between servers begins where a pool of identical instances stands behind one address.

A node selection algorithm appears, health checks, retrying the request on a neighboring server upon failure, and session stickiness to the selected node if the application stores state locally. Here it is already a load balancer, and its configuration is about fault tolerance, not routing.

The choice of server role should be made based on two questions: how many application instances there are and who is responsible for the entrance. One backend — proxying with caching and compression is enough.

A pool under load — a load balancer with checks and a clear retry policy is needed. In Nginx, both scenarios are described in one config, and the transition between them is adding server groups, not migrating to another tool.

Common technical questions before starting work

Before starting work, what is checked is not timelines but risks: what will happen to availability during migration, whether the configuration will be compatible with the current environment, and whether everything will have to be rewritten from scratch.

Below are the five questions most often asked before configuring nginx, with substantive answers for each.

Downtime during configuration migration — how much of a risk is it?

Edits are assembled on a copy and tested in a staging environment; production traffic does not go there. Switching takes seconds and does not break active connections, and the previous configuration file is preserved — rollback is done with one command.

Will this be compatible with what we already have?

First we capture the current environment: build, enabled modules, proxying scheme, and routing rules. New rules are layered on top of existing ones, not instead of them — we change only what hinders operation.

We have our own configuration; it was written by the previous contractor

We analyze the existing files and preserve everything that works: redirects, caching, access restrictions. We do not discard unclear blocks but mark them with comments — so it is visible what was configured and why.

Who will maintain this after handover?

We provide a configuration map, routing scheme, and list of typical operations — from certificate replacement to rolling back an edit. We run a walkthrough with your team so that maintenance does not depend on us.

How will we make sure everything works?

We check against a list: configuration syntax, redirect correctness, security headers, automatic certificate renewal. Separately, we observe behavior under load in typical scenarios — before switching production traffic.

Let's discuss your task: what to send for an estimate and plan

To discuss the task substantively and come back with an estimate and work plan rather than general wording, we need a snapshot of the infrastructure.

Below are the infrastructure inputs that are usually enough to calculate the scope of edits and timelines; whatever is not at hand, we will capture ourselves during the access stage.

  • Deployment scheme: where Nginx stands — as a separate node, in front of the application, behind an external load balancer, or in a container; how many instances there are and how traffic is distributed between them.
  • Traffic peak profile: daily and weekly request maximums, share of static and dynamic content, typical size of served files, and whether there are seasonal spikes.
  • Current configuration: working configs and included fragments, the current branch, modules built separately from the main build.
  • Encryption termination and domains: where certificates converge, how many names are served, whether redirects between schemes are needed, and a common entrance for all domains.
  • Caching, compression, and proxying: which cache headers are already served, and how buffers and timeouts to upstream services are configured.
  • Logs and monitoring: record format, where they are written, how rotation is configured, and which metrics are collected from load balancing and applications.

The project handover order is especially important: who holds access to servers and DNS, whether there is a change window and a staging environment where edits are tested before production.

After that, we will come back with a plan — the sequence of changes and what we check after each step.