Web Server

Posted on 18 2026

nginx is the web server and reverse proxy for this network. Every service accessible via HTTPS, whether that is Nextcloud, SnappyMail, the ebook library, or the mail autoconfig endpoint, reaches the internet through nginx. It handles TLS termination, HTTP/2, security headers, rate limiting, and routing requests to the appropriate backend service.

The source material installs nginx from source, which provides more control over compiled modules but significantly increases maintenance overhead. On Ubuntu 24.04 with regular security updates, the packaged version of nginx from the official nginx repository is current and well-maintained. This series installs from the package repository.

The role of nginx in this network

nginx in this setup serves three distinct purposes.

Reverse proxy: backend services (Nextcloud, SnappyMail, Kavita, SnappyMail) listen on internal ports on localhost or within the container network. nginx receives public HTTPS connections, validates the TLS certificate, and forwards the request to the appropriate backend. Backends never receive direct connections from the internet.

TLS termination: all TLS handling happens at nginx. Backend services receive plain HTTP from nginx over the internal network. Certificates are managed in one place (the nginx container) rather than per service. Let’s Encrypt certificate renewal is centralised.

Static file serving: the mail autoconfig XML, the MTA-STS policy file, any static websites, and other static content are served directly by nginx without involving a backend service. nginx is extremely efficient at this.

Container setup

nginx runs in its own LXC container. Clone the base template:

pct clone 100 103 --hostname web --full
pct start 103

Inside the container:

hostnamectl set-hostname web.yourdomain.net
sed -i 's/base-template/web/g' /etc/hosts

Installation

The Ubuntu repositories include nginx but the version may lag. The official nginx package repository provides the current stable and mainline releases.

Add the official nginx repository:

sudo curl -fsSLo /usr/share/keyrings/nginx-archive-keyring.gpg \
    https://nginx.org/keys/nginx_signing.key

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
    http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | \
    sudo tee /etc/apt/sources.list.d/nginx.list

sudo apt update
sudo apt install nginx

Verify the installation:

nginx -v

Enable and start nginx:

sudo systemctl enable --now nginx

Test the default configuration:

curl -I http://localhost

The response should be 200 OK from the nginx default page.

Why not install from source

The source material compiles nginx from source to include specific modules and the latest version. On Ubuntu 24.04 in 2026, this is not necessary:

The official nginx package repository tracks current stable releases closely. The gap between the packaged version and the latest source version is typically days, not months.

The modules needed for a typical self-hosted setup (HTTP/2, SSL, gzip, proxy, headers, real_ip) are all compiled into the standard nginx package. The source installation in the original series adds ngx_pagespeed and specific OpenSSL versions that are no longer relevant in the same way.

Compiling from source means manually managing security updates, tracking version dependencies, and rebuilding when OpenSSL patches are released. The packaged version handles all of this automatically with apt upgrade.

nginx module check

Verify the installed nginx has the modules needed for this setup:

nginx -V 2>&1 | tr ' ' '\n' | grep -- '--with'

Key modules that should be present:

--with-http_ssl_module
--with-http_v2_module
--with-http_gzip_static_module
--with-http_proxy_module
--with-http_realip_module
--with-http_headers_module
--with-http_auth_request_module
--with-stream
--with-stream_ssl_module

If any are missing, the nginx-extras package from the Ubuntu repository adds most optional modules:

sudo apt install nginx-extras

What this section covers

The nginx section works through configuration in layers, from the global settings that apply to every virtual server, down to the individual site configurations:

Installation and core configuration: the global nginx.conf, worker settings, logging, and the configuration structure.

TLS configuration: the shared TLS settings applied to every HTTPS virtual server, including HSTS, OCSP stapling, session caching, and cipher suite selection.

Security headers: Content Security Policy, X-Frame-Options, Referrer-Policy, and other security headers applied globally and per site.

Virtual servers: individual site configurations for each publicly accessible service, including the reverse proxy settings for each backend.

Certbot and Let’s Encrypt: obtaining and automatically renewing TLS certificates for all public hostnames.

Rate limiting and access control: protecting against scanners, brute force, and abusive clients.

The configuration structure

nginx configuration on Ubuntu is organised under /etc/nginx/:

/etc/nginx/
├── nginx.conf              # Main configuration file
├── conf.d/                 # Global includes (TLS, security headers, etc.)
│   ├── ssl.conf            # Shared TLS configuration
│   ├── security.conf       # Security headers
│   └── gzip.conf           # Compression settings
├── sites-available/        # Site configurations (inactive)
│   ├── default
│   ├── nextcloud
│   ├── snappymail
│   └── ...
└── sites-enabled/          # Symlinks to active site configurations
    └── nextcloud -> ../sites-available/nextcloud

The sites-available / sites-enabled pattern allows site configurations to be written and tested without activating them, and activated with a single symlink.

Global settings in conf.d/ are included by every virtual server. Per-site settings override them where needed.

Verifying configuration

After any configuration change, always validate before reloading:

sudo nginx -t

No output means no errors. If errors appear, fix them before reloading. An nginx reload with a configuration error leaves the previous configuration running, which is the safe behaviour, but a syntax error on startup means nginx does not start at all.

Apply configuration changes without dropping existing connections:

sudo systemctl reload nginx

Logs

nginx logs to two files by default:

# Access log (all requests)
sudo tail -f /var/log/nginx/access.log

# Error log (errors and warnings)
sudo tail -f /var/log/nginx/error.log

# Or via journald
sudo journalctl -u nginx -f

Per-site logs are configured in each virtual server block. By convention in this series, each site logs to named files:

/var/log/nginx/nextcloud.access.log
/var/log/nginx/nextcloud.error.log

This keeps the access log clean and makes per-service log analysis straightforward.

nginx configuration mistakes are easy to make and the error messages are not always obvious. The nginx -t validation step is non-negotiable. Run it before every reload. The most common mistakes are missing semicolons at the end of directives, unmatched braces, and incorrect file paths in include and root directives.