LPIC-2 HTTP Services: Apache, TLS, and Nginx Reverse Proxy
- Host several sites on one Apache server with name-based virtual hosts and protect them with htpasswd
- Terminate HTTPS in Apache by wiring up a certificate, private key, and chain — and explain why the name on the cert has to match
- Turn Nginx into a reverse proxy and load balancer with upstream and proxy_pass
- Preserve the real client's identity to a backend with the Host and X-Forwarded-For headers
- Make validation-before-reload a reflex so a typo never takes a live site offline
Why one server hosts many sites
Understand the problem virtual hosts solve and the vocabulary used throughout.
A single machine, with a single IP address, almost always serves more than one website.
shop.example.comandblog.example.comcan live on the same box, the same port 443, even the same process. The web server has to decide which site a request is for and hand back the right content.The mechanism is the virtual host (vhost). Think of the server as a receptionist at a shared office building: many companies sit behind one front desk and one street address. When you walk in you say who you're here to see — "I'm here for Acme" — and the receptionist routes you to the right floor. On the web, the visitor states who they want in the HTTP `Host:` header (and, for HTTPS, in the TLS SNI field), and the server matches that against each vhost's configured name.
Apache calls this block a
<VirtualHost>and matches on `ServerName`. Nginx calls it aserver {}block and matches on `server_name`. Same idea, two dialects.Apache name-based virtual hosts
Define two sites in Apache and route between them by ServerName.
Each site gets its own
<VirtualHost>block. The key line is `ServerName` — that's the name Apache compares against the incomingHost:header.DocumentRootsays where that site's files live. Below, two sites share one address and port; the name is the only thing that tells them apart.Example — not runnable on your lab box
cat /etc/apache2/sites-available/shop.conf<VirtualHost *:80> ServerName shop.example.com DocumentRoot /var/www/shop </VirtualHost> <VirtualHost *:80> ServerName blog.example.com DocumentRoot /var/www/blog </VirtualHost>Both blocks listen on *:80— the same IP and port. Apache picks between them purely by matching the request'sHost:header against eachServerName. A request forshop.example.comis served from/var/www/shop; one forblog.example.comfrom/var/www/blog. The first vhost listed also becomes the default for any request whose name matches nothing.Check yourself
Two <VirtualHost *:80> blocks listen on the same IP and port. What does Apache use to decide which one serves a given request?
Validate, then reload — never the other way around
Build the habit that keeps a config typo from taking the site down.
You changed a config. Before you make Apache re-read it, prove the file parses. Apache won't start — or restart — with a broken config, so reloading a syntax error on a running server can drop the site entirely until you fix it. The fix is one cheap command first.
Example — not runnable on your lab box
apachectl configtestSyntax OK
- configtest
- Parse every config file and report errors WITHOUT touching the running server. Same as
apachectl -t/httpd -t.
Syntax OKis your green light. If you'd left a missing closing</VirtualHost>or a typo'd directive, you'd see the exact file and line number here instead — and the running server would still be happily serving the old, good config because nothing has reloaded yet.Example — not runnable on your lab box
sudo apachectl graceful- graceful
- Re-read config and restart workers AFTER they finish their current request — no dropped connections.
- restart
- Hard restart: kills workers immediately, interrupting in-flight requests. Avoid on a busy site.
gracefultells each Apache worker to finish the request it's handling, then pick up the new config. Active visitors never see a blip. Pairconfigtestthengracefuland a deploy becomes boringly safe — which is exactly what you want from a deploy. Silent on success — a graceful reload finishes in-flight requests on the old workers before swapping in the new config, so live connections never drop.apachectl configtestfirst, always.Password-protect a site with htpasswd
Add HTTP Basic Auth — and avoid the flag that silently wipes every user.
The simplest way to gate a directory is HTTP Basic Auth backed by an
.htpasswdfile: a list of usernames and hashed passwords. You create entries with thehtpasswdtool, then point a vhost or<Directory>at the file withAuthType BasicandAuthUserFile.The very first user is special — the file doesn't exist yet, so you have to create it. After that, the file exists and you only add to it.
Example — not runnable on your lab box
sudo htpasswd -c /etc/apache2/.htpasswd aliceNew password: Re-typing new password: Adding password for user alice
- -c
- CREATE the file. Use this ONCE, for the very first user only. It OVERWRITES any existing file.
-ccreates a fresh.htpasswdwithaliceas the sole entry. The password is prompted for (never passed on the command line, where it'd land in your shell history) and stored hashed, not in plaintext.Example — not runnable on your lab box
sudo htpasswd /etc/apache2/.htpasswd bobNew password: Re-typing new password: Adding password for user bob
No `-c` this time. The file already exists, so we just append bob. Now bothaliceandbobare in the file. This is the command you'll use for every user after the first.Check yourself
Your .htpasswd already has five users. You run
htpasswd -c .htpasswd newuser. How many users are in the file now?Serve it over HTTPS (TLS)
Wire up a cert, key, and chain — and see why the cert's name must match ServerName.
Turning a site to HTTPS means terminating TLS. Apache's
mod_sslneeds three things and one switch:- `SSLCertificateFile` — the server's public certificate (proves identity, signed by a CA)
- `SSLCertificateKeyFile` — the matching private key (kept secret; mathematically paired to the cert)
- `SSLCertificateChainFile` — the intermediate chain linking your cert up to a root the browser trusts
- `SSLEngine on` — the switch that actually enables TLS for this vhost
Think of it as a passport at border control: the certificate is the passport (it states a name and is stamped by an authority), the private key proves you're its real holder, and the chain is the issuing-authority paperwork the officer trusts.
Example — not runnable on your lab box
cat /etc/apache2/sites-available/shop-ssl.conf<VirtualHost *:443> ServerName shop.example.com DocumentRoot /var/www/shop SSLEngine on SSLCertificateFile /etc/ssl/certs/shop.example.com.crt SSLCertificateKeyFile /etc/ssl/private/shop.example.com.key SSLCertificateChainFile /etc/ssl/certs/chain.pem </VirtualHost>This is the HTTPS twin of the port-80 vhost, now on *:443with TLS turned on. The cert and key are a matched pair — the key must be the one the cert was issued for, orconfigtestfails with a key-mismatch error. Keep the key under/etc/ssl/private/with tight permissions; it's the one secret that, if leaked, breaks everything.Check yourself
A browser visiting https://shop.example.com gets a certificate error even though Apache is running and SSLEngine is on. What's the most likely cause?
Nginx as a reverse proxy and load balancer
Use upstream and proxy_pass to put Nginx in front of backend apps — and pass the client through.
Nginx's
server {}block is its vhost, matched byserver_name— the direct analogue of Apache's<VirtualHost>/ServerName. But Nginx's signature job is the reverse proxy: instead of serving files itself, it forwards requests to one or more backend application servers and relays their responses.List the backends in an `upstream {}` block, then
proxy_passto that group. With more than one backend, Nginx spreads requests across them — that's load balancing for free. Picture Nginx as the building's receptionist again, but now also a dispatcher: it takes every visitor and sends them to whichever caseworker (backend) is free.Example — not runnable on your lab box
cat /etc/nginx/conf.d/shop.confupstream shop_backends { server 192.0.2.21:8080; server 192.0.2.22:8080; } server { listen 80; server_name shop.example.com; location / { proxy_pass http://shop_backends; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }The upstreamblock names a pool of two app servers. Theserverblock is the vhost forshop.example.com;proxy_pass http://shop_backendshands matching requests to the pool, alternating between the two backends by default (round-robin). Nginx is now a load balancer — add or removeserverlines to scale the pool.Round-robin assumes every backend is equal and always wanted. Two upstream parameters cover the cases it doesn't:
- `backup` marks a server as standby — Nginx sends it no traffic while any non-backup server in the pool is up, and falls back to it only when they are all down. That is how you keep a spare out of rotation until it is actually needed.
- Automatic failover is already on: if a request to one backend fails to connect, Nginx retries it on the next server (that is the default
proxy_next_upstream error timeout), so one dead primary never reaches the user as an error.max_fails=andfail_timeout=control how many failures mark a server down and for how long.
Example — not runnable on your lab box
cat /etc/nginx/conf.d/shop.confupstream shop_backends { server 192.0.2.21:8080; # primary server 192.0.2.22:8080; # primary server 192.0.2.23:8080 backup; # standby — only used when both primaries are down }The first two servers share traffic round-robin; 192.0.2.23sits idle withbackupuntil both primaries are unreachable, then it serves everything. Because failover is automatic, stopping one primary just shifts its share to the other with no error reaching the client — exactly what a load balancer in front of app servers is for.Example — not runnable on your lab box
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
- -t
- Test the config and exit — validate WITHOUT reloading. Nginx's apachectl-configtest equivalent.
Same discipline as Apache: prove it parses before you reload. Only after test is successfuldo you runsudo nginx -s reload(orsystemctl reload nginx) to apply it gracefully. Validate, then reload — every time, for every server.Check yourself
After putting Nginx in front of an app, the backend's logs show every request coming from Nginx's IP instead of real visitors. Which header fixes this?
Try it in your live terminal
Do it, don't just read it — these safe, read-only checks run on the real sandbox to your right.
You have a real shell. These commands are read-only — they report what web software is present and whether anything is answering on port 80, without changing a thing. Run them in order to orient yourself the way you would on an unfamiliar server.
apachectl -v 2>/dev/null || httpd -v 2>/dev/null || echo 'no apache'Is Apache installed, and which version? It tries the Debian name ( apachectl) then the RHEL name (httpd), and printsno apacheif neither exists — so the check works on any distro and never errors out.nginx -v 2>&1 || echo 'no nginx'Same question for Nginx. nginx -vprints its version to stderr, so2>&1makes sure you see it;|| echo 'no nginx'covers the case where Nginx isn't installed.curl -sI http://localhost 2>/dev/null | head -1Is anything actually serving HTTP here and now? -sIdoes a silent HEAD request (headers only, no body);head -1keeps just the status line, e.g.HTTP/1.1 200 OK. Empty output means nothing is listening on port 80 — itself a useful finding.
- Name-based virtual hosts let one IP serve many sites: Apache matches
ServerName, Nginx matchesserver_name, both against the client's requested name. - Validate before you reload — every time.
apachectl configtest/nginx -tfirst, thenapachectl graceful/ reload. A typo reloaded onto a live server takes the site down. htpasswd -ccreates (and overwrites) the password file — use it once, ever. Drop-cto add users; re-running it wipes everyone already in the file.- TLS needs cert + key + chain with
SSLEngine on, and the cert's CN/SAN must matchServerNameor the browser rejects it. SNI lets many HTTPS vhosts share one IP. - Nginx
upstream {}+proxy_passturn it into a reverse proxy / load balancer — and you must forwardHostandX-Forwarded-Forso the backend sees the real site and the real client.
| Directive or command | Server | What it does | Watch for |
|---|---|---|---|
ServerName / server_name | Apache / Nginx | Routes a request to a vhost by the name the client asked for | Must match the Host: header |
apachectl configtest / nginx -t | Apache / Nginx | Parse-checks config without reloading | Run it BEFORE any reload |
apachectl graceful | Apache | Reloads config, finishing in-flight requests first | No-downtime reload; never reload an untested config |
htpasswd -c file user | Apache | Creates the password file with one user | OVERWRITES the file — use once only |
htpasswd file user | Apache | Appends a user to an existing file | The safe way to add more users |
SSLEngine on + cert/key/chain | Apache | Terminates HTTPS for the vhost | Cert CN/SAN must match ServerName |
upstream {} + proxy_pass | Nginx | Reverse-proxies / load-balances to backend servers | Forward Host and X-Forwarded-For |
