Where to start First steps track · 15 stages
Linux Labs

nginx — web server and reverse proxy

cheat sheet · Ubuntu 24.04 · nginx 1.24 https://lab.dimsanych.ru/en/wiki/nginx/

Cheat sheet

  • sudo nginx -t check the config (sudo only)
  • sudo systemctl reload nginx apply, keep connections
  • sudo nginx -T | grep -n server_name which sites nginx sees
  • ls -l /etc/nginx/sites-enabled/ enabled sites
  • curl -i -H 'Host: docs.lab.test' http://127.0.0.1/ who answers
  • sudo tail -n 5 /var/log/nginx/error.log why 404, 403, 502
  • sudo tail -n 5 /var/log/nginx/access.log what was asked, the code
  • sudo ss -tlnp | grep nginx which ports it listens on

Configuration

The Debian and Ubuntu layout:

  • /etc/nginx/nginx.conf — common settings, with an include of sites at the bottom;
  • /etc/nginx/sites-available/ — site files;
  • /etc/nginx/sites-enabled/ — links to enabled sites; nginx reads only these;
  • /var/log/nginx/access.log and error.log — the logs.

Enable a site

bash
sudo ln -s /etc/nginx/sites-available/docs /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

A file in sites-available without a link in sites-enabled is invisible to nginx. To disable a site, remove the link; the file stays.

nginx -t checks the whole config and names the file and line of an error. reload re-reads the config without dropping connections; with an error in the config nginx keeps the old one. systemctl reload returns at once, while the new worker processes take connections a moment later: a curl on the same line may still see the old state.

Logs

access.log has a line for every request, error.log says why a request failed and what broke at start. The default access.log format is combined:

text
127.0.0.1 - - [20/Oct/2026:10:03:58 +0000] "POST / HTTP/1.1" 405 166 "-"

Field by field: $remote_addr (client address), -, $remote_user, [$time_local], "$request" (method, path, version), $status (code), $body_bytes_sent (body size), "$http_referer", "$http_user_agent" — the user agent "curl/8.5.0" did not fit in the example. log_format defines your own format — in a site file outside the server block:

nginx
log_format timed '$remote_addr [$time_local] "$request" '
                 '$status $body_bytes_sent $request_time';
access_log /var/log/nginx/app.log timed;

$request_time is how many seconds nginx spent on the request. Files with .1 and .2.gz next to a log are older ones that logrotate moves aside and compresses on schedule (/etc/logrotate.d/nginx).

Proxy: path and headers

Whether there is a URI after the address in proxy_pass decides which path the application receives:

location /api/ and proxy_pass Request /api/x goes out as
http://127.0.0.1:3003 /api/x — path unchanged
http://127.0.0.1:3003/ /x — /api/ replaced with /

By default the application sees Host: 127.0.0.1:3003 — the address from proxy_pass. The site name and the client address are passed in headers:

nginx
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

HTTPS and the redirect from HTTP

nginx
server {
    listen 443 ssl;
    server_name shop.lab.test;
    ssl_certificate     /etc/nginx/tls/fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/shop.key;
    root /var/www/shop;
}
server {
    listen 80;
    server_name shop.lab.test;
    return 301 https://$host$request_uri;
}

ssl_certificate is the chain file: the site certificate first, the intermediate after it. $request_uri is the path with the query after ?: http://shop.lab.test/x?y=1 redirects to https://shop.lab.test/x?y=1. Certificates and how they are checked — openssl-tls.

Loading…