On this page
nginx serves files and passes requests to applications running behind it. Installing it is one command on any of the distributions below. The part worth reading is where each family expects your configuration to go, because that differs, and copying the wrong layout is the usual first mistake.
Installing
On Debian and Ubuntu:
apt update
apt install -y nginxOn RHEL, Alma, and Rocky:
dnf install -y nginxThen start it and have it come back after a reboot:
systemctl enable --now nginx
systemctl status nginxConfirm it answers on the machine itself before you involve the network:
curl -I http://127.0.0.1/Opening the ports
A working nginx that nobody can reach is a firewall problem, and it needs two things, not one.
In the host firewall, with ufw:
ufw allow 80/tcp
ufw allow 443/tcpor with firewalld:
firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reloadThen add inbound rules in the panel for TCP 80 and TCP 443. See Port rules, and Using a firewall on your Linux server for how the two relate.
Where the configuration lives
| Debian and Ubuntu | RHEL, Alma, and Rocky | |
|---|---|---|
| Main file | /etc/nginx/nginx.conf | /etc/nginx/nginx.conf |
| Where you add a site | /etc/nginx/sites-available/, then symlink into /etc/nginx/sites-enabled/ | /etc/nginx/conf.d/, one .conf file per site |
| Default site | /etc/nginx/sites-available/default | A server block inside nginx.conf itself |
| Default document root | /var/www/html | /usr/share/nginx/html |
| Worker user | www-data | nginx |
The default site answers any request that does not match one of your
server_name values, so it can quietly shadow a site you thought you had
configured. On Debian and Ubuntu, remove the symlink once your own site works:
rm /etc/nginx/sites-enabled/defaultOn the RHEL family the default lives in nginx.conf, so comment out that
server block rather than deleting the file.
Serving a static site
Create the directory and put your files in it:
mkdir -p /var/www/example.com
chmod -R a+rX /var/www/example.comThe worker user needs to read the files and traverse every directory above them.
That is what a+rX gives you: read on files, and execute only on directories.
Write the server block. On Debian and Ubuntu the file is
/etc/nginx/sites-available/example.com; on the RHEL family it is
/etc/nginx/conf.d/example.com.conf. The contents are the same:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}Add listen [::]:80; only if your server has IPv6. On a machine without it,
nginx fails to start on that line.
On Debian and Ubuntu, enable the site by linking it:
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/On the RHEL family, files in conf.d are active as soon as they exist. Either
way, test and reload:
nginx -t
systemctl reload nginxReverse proxy to an application port
Run your application on loopback so it is only reachable through nginx, then proxy to it:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
}The Upgrade and Connection headers are what let WebSocket connections
through. Without them a WebSocket handshake gets a plain HTTP response and the
application appears to connect and then go silent.
Check the application is listening where you think it is:
ss -tlnp | grep 3000If it shows 0.0.0.0:3000, the application is reachable directly from the
internet as well as through nginx. Bind it to 127.0.0.1 in its own
configuration, and do not rely on the firewall to hide it.
Test before you reload
nginx -tThat parses the whole configuration, including every included file, and checks that the certificates and roots it references exist. It prints the file and line of the first problem. Only when it passes:
systemctl reload nginxReload starts new workers with the new configuration and lets the old ones finish
what they were doing, so no request is dropped. Use restart only when you have
changed something a reload cannot pick up, such as the worker user, and know that
it drops connections in progress.
nginx -t does not check anything that only exists at request time. A wrong
proxy_pass port, an application that is not running, or a DNS name that does
not resolve all pass the test and fail on the first request.
Where the logs are
The defaults are /var/log/nginx/access.log and /var/log/nginx/error.log. Per
site, use the access_log and error_log directives shown above, which keeps
one site's traffic out of another's file.
tail -f /var/log/nginx/error.logIf nginx will not start at all, the reason is often not in the error log yet, because it failed before opening it. Look at the service instead, as described in Managing services with systemd:
journalctl -u nginx -n 50 --no-pagerWhen it will not start or will not serve
Address already in use. Something else holds port 80, often another web server installed with a control panel:
ss -tlnp | grep ':80'403 on files you can read as root. Every directory in the path needs execute
permission for the worker user, and a file under a home directory usually fails
that. Serve from /var/www and set a+rX as above.
403 on the RHEL family with correct permissions. SELinux is refusing a document root outside the default. Label it:
dnf install -y policycoreutils-python-utils
semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com(/.*)?"
restorecon -Rv /var/www/example.comA duplicate server_name warning from nginx -t. Two server blocks claim
the same name on the same port, usually because a site is both in conf.d and in
nginx.conf, or linked twice. The first one loaded wins and the other is ignored.