Who will answer a name
The first command shows the answer as a client sees it, the second which
listen and server_name nginx actually read, in file order.
nginx serves sites from disk and forwards requests to applications; it picks a site by Host, nginx -t checks the config.
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
nginx is a web server: it serves site files from disk and works as a
reverse proxy — it forwards a request to an application
and returns its response to the client. The nginx service runs under
systemd: the master process reads the config as root, worker processes
serve requests as www-data. Everything nginx opens on disk or in a socket is
checked against www-data's permissions.
Each site is a server block. A request first goes to the blocks that listen
on its address and port (listen), then nginx compares the
Host header with their server_name.
If a name matches, that block answers. If none matches, the
default server answers: the block with
listen 80 default_server, or the first block for that port if none is marked.
On Ubuntu the default server on port 80 is the default site with the
"Welcome to nginx!" page. So a typo in the name or a forgotten site gives
someone else's page, not an error.
nginx looks for a file at root + the request path. With
root /var/www/docs a request for /guide/a.html means the file
/var/www/docs/guide/a.html. For a directory request (/) it serves a file
from the index list (index.html by default).
A location block sets rules for some paths. Of the
prefixes the path starts with, the longest wins: location /api/ beats
location / for /api/users.
| Directive | Path for the request /files/x.txt |
|---|---|
location /files/ { root /srv/data; } |
/srv/data/files/x.txt |
location /files/ { alias /srv/data/; } |
/srv/data/x.txt |
root appends the whole path, alias replaces the matched prefix.
nginx opens a connection to the application — the upstream —
and forwards the request. If it cannot connect, the client gets
502 Bad Gateway; if it gives up waiting, 504 Gateway Time-out. These are
nginx's own responses: such requests never reach the application log. The
backend can also listen on a unix socket:
proxy_pass http://unix:/run/app/app.sock;.
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.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.
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:
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:
$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).
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:
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.
| Directive | What it does | Example |
|---|---|---|
listen server |
The block's address and port; default_server makes it the default server, ssl turns on TLS. | listen 443 ssl; |
server_name server |
Site names nginx compares the Host header against. | server_name docs.lab.test www.docs.lab.test; |
root server, location |
The site directory; file path = root + request path. | root /var/www/docs; |
index server, location |
Which file to serve when a directory is requested. | index index.html; |
location server |
Rules for paths with this prefix; the longest matching prefix wins. | location /api/ { … } |
alias location |
A directory that replaces the matched location prefix (root appends the whole path). | alias /srv/data/; |
try_files server, location |
Check files in order and serve the first that exists, otherwise the last option. | try_files $uri $uri/ =404; |
proxy_pass location |
Forward the request to a backend; a URI after the address replaces the matched location prefix. | proxy_pass http://unix:/run/app/app.sock; |
proxy_set_header server, location |
A request header for the backend; without it Host is the address from proxy_pass. | proxy_set_header Host $host; |
proxy_read_timeout server, location |
How long to wait for the backend's response before 504. | proxy_read_timeout 30s; |
ssl_certificate server |
The certificate file; with an intermediate CA, the site certificate first, then the intermediate. | ssl_certificate /etc/nginx/tls/fullchain.pem; |
ssl_certificate_key server |
The private key for the first certificate in the ssl_certificate file. | ssl_certificate_key /etc/nginx/tls/site.key; |
return server, location |
Answer immediately with a code; 301 with a URL is a redirect. | return 301 https://$host$request_uri; |
client_max_body_size http, server, location |
The largest request body; anything bigger gets 413. | client_max_body_size 20m; |
error_log http, server |
Where to write errors; level warn, error, crit and above. | error_log /var/log/nginx/docs.error.log; |
access_log http, server, location |
Where and in which format to log a line per request. | access_log /var/log/nginx/app.log timed; |
log_format http |
A custom log format built from variables — $status, $request_time and others. | log_format timed '$remote_addr "$request" $status $request_time'; |
Nothing found. Try a shorter query.
The first command shows the answer as a client sees it, the second which
listen and server_name nginx actually read, in file order.
The error.log line names the upstream — where nginx went. If the
application answers directly, the problem is in proxy_pass or permissions; if
not, it is in the application.
The request's Host matched no server_name, so the default server answered.
Check the name in server_name and that the site's link exists in
sites-enabled: sudo nginx -T | grep server_name.
error.log has the path nginx built:
open() "/var/www/portal/nope.html" failed (2: No such file or directory).
Compare it with the real path of the file: usually root or alias is wrong.
(13: Permission denied) — www-data cannot read the file or enter a
directory on the path; namei -l shows permissions along the whole path.
directory index of "…" is forbidden — the directory has no file from
index.
nginx did not open a connection to the backend. The error.log line names the
reason:
| In error.log | Meaning |
|---|---|
(111: Connection refused) |
nothing listens on the address or socket |
(13: Permission denied) |
www-data has no right to the socket |
(2: No such file or directory) |
no socket file at that path |
In full: connect() failed (111: Connection refused) while connecting to
upstream, … upstream: "http://127.0.0.1:3001/". For a socket —
connect() to unix:/run/app/app.sock failed (13: Permission denied). nginx
logs errors 13 and 2 at level [crit] and 111 at [error]: a search for
[error] alone misses them. A socket file left behind after the application
stopped also gives 111. Socket permissions and paths — unix-sockets.
There is a connection but no answer: upstream timed out (110: Connection
timed out) while reading response header from upstream. The application hangs
or answers slower than proxy_read_timeout (60 s by default).
unexpected "}" in …:5 — the line before } has no ;;a duplicate default server for 0.0.0.0:80 — default_server is set in two
blocks on one port;open() "/run/nginx.pid" failed (13: Permission denied) — nginx -t was run
without sudo.[warn] conflicting server name "docs.lab.test" on 0.0.0.0:80, ignored — one
name sits in two blocks on one port. The test still passes, but requests for
that name go to the block loaded first, and the second never gets them. Find
both:
grep -r on the directory does not work here: it skips the links in
sites-enabled.
SSL_CTX_use_PrivateKey(…) failed (… key values mismatch) — the first
certificate in the ssl_certificate file does not match the key. Either the key
is wrong, or the chain file has the intermediate first. The site certificate
goes first.
nginx reads certificates at start and on reload. After replacing the file —
sudo nginx -t && sudo systemctl reload nginx, then check a second later.
sudo ss -tlnp | grep ':80 ' shows who holds the port. Stop it or change
listen.
Changing the address on the same port (listen 80 → listen 127.0.0.1:80)
does not work through reload: systemctl reload returns 0, nginx stays on
the old sockets, and error.log says bind() to 127.0.0.1:80 failed (98:
Address already in use). Only sudo systemctl restart nginx applies such a
change; sudo ss -tlnp | grep nginx shows what really listens.
On reload with an error the old config keeps working; restart stops nginx
and cannot start it again: every site on the machine is down. Before applying
anything — sudo nginx -t.
reload, restart, status.Found an inaccuracy? Report an error in the article