Module 7 · 7.2
English
A web server with nginx
Module 7: Domain, web server and https · Reading time: 12 minutes
Learning objective: After this lesson you can install nginx, set up a website and a reverse proxy with a server block, test and reload a configuration safely, and set the permissions of your webroot correctly.
Key concepts: nginx, server block, sites-available and sites-enabled, conf.d, listen, server_name, root, index, try_files, default server, nginx -t, reload, reverse proxy, proxy_pass, Host, X-Forwarded-For, X-Forwarded-Proto, webroot, PHP-FPM
What a web server does
A web server listens on port 80 (http) and 443 (https), accepts requests from browsers and sends something back: a file from disk (a static site), or the response of a program running behind it (PHP, a Node.js app, a container). In that second case the web server is called a reverse proxy: it sits at the front, handles https, and passes the request on to the application, which only listens on 127.0.0.1.
On Linux, nginx and Apache are the best-known web servers, and Caddy (lesson 7.4) is a modern alternative that handles certificates by itself. This lesson uses nginx: it is in every distribution, is light on memory and is the default in many guides.
Installing and a first look
anna@web1:~$ sudo apt install nginx
anna@web1:~$ systemctl status nginx
anna@web1:~$ curl -I http://localhost
HTTP/1.1 200 OK
Server: nginx/1.26.3
Content-Type: text/html(The version number is an example.) Don't forget the firewall: ports 80 and 443 must be open (lesson 5.2). If you now go to http://192.0.2.10, you will see the nginx welcome page.
The configuration lives in /etc/nginx/:
| Path | What |
|---|---|
nginx.conf | main file; you usually leave it alone |
sites-available/ | one file per site (Debian and Ubuntu) |
sites-enabled/ | links (symlinks) to the sites that are enabled |
conf.d/*.conf | separate settings that are always loaded |
snippets/ | snippets you reuse in several sites |
sites-available and sites-enabled are a Debian and Ubuntu convention: you enable or disable a site by creating or removing a link, without deleting the file.
A first site
Create a folder for the site and a test page:
anna@web1:~$ sudo mkdir -p /var/www/example.com
anna@web1:~$ echo '<h1>Het werkt</h1>' | sudo tee /var/www/example.com/index.htmlThen create a server block: the description of one site.
# /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
}Read it like this:
listen 80;andlisten [::]:80;make nginx listen on IPv4 and IPv6. If you forget the second line, your AAAA record won't work (lesson 7.1).server_namesays which names this block applies to. For every request, nginx looks at the name the browser asks for (the Host header) and picks the block that matches.rootis the folder with the files,indexthe file shown when a folder is requested.try_filestries the requested file, then a folder, and otherwise returns a 404. Without a line like this, a wrong configuration can show more than you want.- Separate log files per site make searching much easier (lesson 6.2).
Enable the site, remove the default site, test and reload:
anna@web1:~$ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
anna@web1:~$ sudo rm /etc/nginx/sites-enabled/default
anna@web1:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
anna@web1:~$ sudo systemctl reload nginx
anna@web1:~$ curl -I http://example.com
HTTP/1.1 200 OKMake nginx -t before every reload a fixed habit. If there is an error, it tells you which file and which line, and your running site stays untouched. If you restart without testing, nginx won't start with an error and all your sites go down (lesson 6.1).
The default server: what does an unknown name get?
What happens if someone requests your server with a name that isn't in any server_name, or simply with the IP address? Then nginx picks the default server: the block with default_server after listen, or otherwise the first block it came across. If you don't think about it, that will be one of your real sites.
Tidier is a separate block that rejects such requests:
# /etc/nginx/sites-available/000-default
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}444 is a special nginx code: the connection is closed without a response. Scanners that sweep through IP addresses get to see nothing. For https (port 443) a default server works a little differently, because it needs a certificate; you'll come back to that in lesson 7.3.
nginx as a reverse proxy
If an application is running on 127.0.0.1:3000 (lesson 6.3) or a container on 127.0.0.1:3001 (lesson 9.3), nginx makes it available to the outside world:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}The four proxy_set_header lines matter. Without Host the application doesn't know which name it is being visited under. Without X-Forwarded-For the application sees every visit coming from 127.0.0.1 (the proxy) instead of the visitor's real address. Without X-Forwarded-Proto the application thinks everything goes over http, and sometimes creates links or cookies for http, while your site runs over https. Many applications have a setting to trust these headers ("trusted proxy"); only put 127.0.0.1 in it, otherwise any visitor can send a fake address.
Applications that use websockets (live updates, such as Uptime Kuma) need two more lines: proxy_http_version 1.1; and passing on Upgrade and Connection. The application's documentation usually says exactly which ones.
Webroot permissions
On Debian and Ubuntu, nginx runs its worker processes as the user www-data. That user must be able to read your files, but shouldn't be able to write them. A good layout (lesson 3.1):
anna@web1:~$ sudo chown -R anna:www-data /var/www/example.com
anna@web1:~$ sudo find /var/www/example.com -type d -exec chmod 750 {} \;
anna@web1:~$ sudo find /var/www/example.com -type f -exec chmod 640 {} \;You (anna) manage the files, the group www-data may read, everyone else gets nothing. If the site needs to write somewhere, such as an uploads folder, give write permission to www-data on that folder only. Never chmod 777 (lesson 10.2). If you get a 403 Forbidden, look in the site's error log: you'll find permission denied with the path there.
A few other habits:
- No secrets in the webroot. Backups (
site.zip),.envfiles and.gitfolders don't belong there; if they end up there by accident, anyone can request them. An extra safety net:location ~ /\. { deny all; }blocks all files starting with a dot (except what you explicitly allow, such as/.well-known/, see lessons 7.3 and 7.4). - Hide the version.
server_tokens off;innginx.confremoves the version number from theServerheader and error pages. That isn't real security, but it gives scanners less information.
PHP, briefly
For PHP sites (such as WordPress), nginx talks to PHP-FPM, a separate service. On Debian you install php-fpm and add a location ~ \.php$ block to the server block with include snippets/fastcgi-php.conf; and fastcgi_pass unix:/run/php/php8.4-fpm.sock; (the version number differs per release). The WordPress course goes into this further.
On AlmaLinux and Rocky Linux
There is no sites-available there: you put each site in its own file in /etc/nginx/conf.d/<naam>.conf. nginx runs as the user nginx instead of www-data, and the default webroot is /usr/share/nginx/html. By default, SELinux doesn't let nginx connect to an application behind proxy_pass; turn that on with setsebool -P httpd_can_network_connect 1. Files outside the usual folders need a SELinux label (semanage fcontext and restorecon).
Summary
- nginx serves files or, as a reverse proxy, passes requests on to an application on
127.0.0.1. - One server block per site in
sites-available, enabled with a link insites-enabled; listen on80and[::]:80. - Always
nginx -tbeforesystemctl reload nginx. - Your own default server with
return 444prevents an unknown name or the bare IP address from showing one of your sites. - As a reverse proxy you pass on
Host,X-Forwarded-ForandX-Forwarded-Proto; the webroot is readable forwww-data, not writable, and free of secrets.
Quiz
Exercise
Exercise O7.2: Your first site. Install nginx (or use Caddy from lesson 7.4), and create a server block for a (sub)domain that points to your server, with listen for IPv4 and IPv6, separate log files and your own default server. Test with nginx -t, reload, and check with curl -I over IPv4 and IPv6. Set the webroot permissions as in the lesson. Fill in the checklist and paste your server block.
Exercise O7.2: Your first site
You do this exercise yourself; no one checks it. Tick off what you've done and mark the exercise as done.
On the bare IP nothing (connection closed) or a neutral page, never a customer site. Only an upload folder writable for www-data, the rest read-only. Behind a proxy: Host, X-Real-IP, X-Forwarded-For and X-Forwarded-Proto.
Your ticks and notes only stay in this browser; we don't send them anywhere. If you're signed in, we only save in My courses that you've done the exercise.
Further reading
- Forbidden (403): You don't have permission to access / on this server
- nginx documentation, How nginx processes a request (server_name and default server): https://nginx.org/en/docs/http/request_processing.html
- nginx documentation, Beginner's Guide: https://nginx.org/en/docs/beginners_guide.html
- nginx documentation,
ngx_http_proxy_module: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
