🖥️

Level 14 of 24

Web Servers

Nginx and Apache: virtual hosts, reverse proxying, and production configuration.

Nginx and Apache are the two web servers you'll administer most often — this level covers configuring both for the single most common real task: serving a site and reverse-proxying to a backend application.

Prerequisites

  • • Level 7: Networking
  • • Level 9: SSH & Remote Administration

By the end of this level, you can

  • ✓ Configure an Nginx server block to serve a site
  • ✓ Configure Nginx as a reverse proxy in front of a backend application
  • ✓ Explain the equivalent Apache concepts (virtual hosts, modules)

Nginx: server blocks and reverse proxying

Serving static content and proxying to a backend app on the same server. · 11 min

A "server block" in Nginx (the equivalent of Apache's "virtual host") defines how Nginx handles requests for a specific domain or port. Configuration files typically live in `/etc/nginx/sites-available/` on Debian-family systems, symlinked into `/etc/nginx/sites-enabled/` to actually activate them — a deliberate two-step pattern that lets you keep a config file present but disabled by simply removing the symlink, without deleting the file itself.

For a static site, the key directives are `listen` (which port), `server_name` (which domain(s) this block handles), and `root` (which directory on disk to serve files from). For dynamic backend applications — a Node.js, Python, or Java app listening on its own local port — Nginx commonly acts as a reverse proxy: it receives the public request on port 80/443 and forwards it internally to the backend's actual port, then returns the backend's response to the client. This lets Nginx handle TLS termination, static asset serving, and load balancing in front of an application that itself only needs to worry about application logic.

After any configuration change, `sudo nginx -t` tests the configuration syntax without applying it — catching an error before it takes down the running server, exactly the same discipline as `sshd -t` from Level 9. Only after a clean test should you `sudo systemctl reload nginx` to apply the change.

CommandPurposeExample
nginx -tTest Nginx configuration syntax without applying it—
systemctl reload nginxApply a valid configuration change without dropping active connections—
ln -s /etc/nginx/sites-available/site /etc/nginx/sites-enabled/Enable a site (Debian-family layout)—

A reverse proxy server block

server {
    listen 80;
    server_name 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;
    }
}

Common Mistakes

  • ⚠ Forgetting the `X-Forwarded-For` / `X-Real-IP` headers on a reverse proxy — without them, the backend application sees every request as coming from 127.0.0.1 (Nginx itself), losing the real client IP entirely
  • ⚠ Editing a config file directly without running `nginx -t` first, and finding out about a typo only after Nginx fails to reload
  • ⚠ Confusing sites-available (where the config lives) with sites-enabled (which controls whether it's active) — a file present in sites-available alone does nothing until symlinked
🧪

Hands-On Lab

Deploy a site behind an Nginx reverse proxy

Objectives

  • ✓ Run a simple backend application on a local port
  • ✓ Configure Nginx to reverse-proxy public traffic to it
  • ✓ Verify the real client IP is correctly forwarded

Instructions

  1. Start a simple backend: `python3 -m http.server 3000` (standing in for a real application).
  2. Install Nginx (`sudo apt install nginx`) and create a new site config at `/etc/nginx/sites-available/proxy-test` using the reverse-proxy example above, pointing `proxy_pass` at `http://127.0.0.1:3000`.
  3. Enable it: `sudo ln -s /etc/nginx/sites-available/proxy-test /etc/nginx/sites-enabled/`, then remove or disable the default site if it conflicts.
  4. Test config with `sudo nginx -t`, then `sudo systemctl reload nginx`.
  5. From another machine (or `curl -I http://<server-ip>`), confirm you reach the backend through Nginx on port 80.
Hints (2)
  • If you get a "connection refused" from Nginx itself, confirm Nginx is actually running: `systemctl status nginx`.
  • If the proxy connects but the backend never responds, confirm the backend is actually listening on the port you configured: `ss -tulpn | grep 3000`.

Takeaway: Always run `nginx -t` before `reload` — it catches configuration errors before they take down a running server, the same discipline that matters for sshd_config.

Apache: virtual hosts and modules

The equivalent Apache concepts, for when a server runs Apache instead of (or alongside) Nginx. · 6 min

Apache HTTP Server uses "virtual hosts" — the direct equivalent of Nginx's server blocks — configured in files typically under `/etc/apache2/sites-available/` on Debian-family systems (or `/etc/httpd/conf.d/` on RHEL-family systems, where the package and service are named `httpd` rather than `apache2`), with the same enable/disable-via-symlink pattern via `a2ensite`/`a2dissite` helper commands on Debian-family systems.

Apache's architecture is heavily module-based: functionality like URL rewriting (`mod_rewrite`), SSL/TLS (`mod_ssl`), and reverse proxying (`mod_proxy`) are separate modules enabled or disabled with `a2enmod`/`a2dismod` (Debian-family) — a config referencing a directive from a module that isn't enabled will fail to start, a common early source of confusion when following a tutorial written for a different setup.

Nginx and Apache can coexist on the same server in real deployments (Nginx as the public-facing reverse proxy/static file server, Apache running the actual application behind it), and neither is universally "better" — Apache's `.htaccess` per-directory configuration override is genuinely convenient for shared hosting scenarios that Nginx deliberately doesn't support, while Nginx's architecture generally handles very high concurrent connection counts more efficiently. Which one a given server runs is usually a legacy or team-preference decision more than a technical necessity to relitigate.

CommandPurposeExample
a2ensite / a2dissiteEnable/disable a virtual host (Debian-family)sudo a2ensite mysite.conf
a2enmod / a2dismodEnable/disable an Apache modulesudo a2enmod proxy_http
apachectl configtestTest Apache configuration syntax before reloading—
systemctl reload apache2Apply configuration changes (apache2 on Debian-family, httpd on RHEL-family)—

Takeaway: Apache virtual hosts map conceptually to Nginx server blocks — the biggest practical difference administrators hit is Apache's module system, where a directive from a disabled module simply won't work until that module is explicitly enabled.

Sponsor / Advertisement