Skip to content

Cookie settings

Choose which kinds of cookies you allow. You can change your choice at any time via Cookie settings at the bottom of every page. Privacy statement

Necessary

Always on

Needed to log in, keep your cart and remember your choices. These are always on.

Which cookies?
  • weboke_sessie, weboke_klant, weboke_remember: your session and staying logged in
  • weboke_basket: your cart
  • wkBtwIncl: showing prices with or without VAT
  • wok_cookies: your cookie choice, 12 months

Measures which ad leads to a visit or order, so we do not pay for ads that do nothing.

Which cookies?
  • Google Ads via Google Tag Manager (_gcl_au, _gcl_aw), up to 90 days
  • OpenAI Ads, ads in ChatGPT
  • wok_aff: which partner referred you to us (affiliate programme), 60 days

4.6

Country and language

Each country has its own site, with prices and VAT for that country.

View

Module 7 · 7.2

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/:

PathWhat
nginx.confmain 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/*.confseparate 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.html

Then 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; and listen [::]:80; make nginx listen on IPv4 and IPv6. If you forget the second line, your AAAA record won't work (lesson 7.1).
  • server_name says 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.
  • root is the folder with the files, index the file shown when a folder is requested.
  • try_files tries 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 OK

Make 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), .env files and .git folders 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; in nginx.conf removes the version number from the Server header 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 in sites-enabled; listen on 80 and [::]:80.
  • Always nginx -t before systemctl reload nginx.
  • Your own default server with return 444 prevents an unknown name or the bare IP address from showing one of your sites.
  • As a reverse proxy you pass on Host, X-Forwarded-For and X-Forwarded-Proto; the webroot is readable for www-data, not writable, and free of secrets.

Quiz

1. You have changed a server block. What is the safe order?



2. Your site works over IPv4 but not over IPv6, even though the AAAA record is correct and the firewall allows IPv6. What is probably missing from the server block?



3. Someone opens http://192.0.2.10 (the bare IP address) and sees the website of one of your customers. How do you prevent that?



4. Your application behind proxy_pass sees the address 127.0.0.1 on every visit. What is missing?



5. Which permissions suit the files of a static website in /var/www/example.com?



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.




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

One moment…

Set up your domains

For each domain, arrange the nameservers, the link to your hosting and who becomes the owner.

Loading your domains…