LAB 01 – Environment, virtual hosts & location matching¶
Story: You just joined PixelShop, a small online shop selling retro gadgets. Your first job as the new platform engineer: put the shop and the admin panel behind one NGINX "edge" server.
| Duration | ~35 min |
| You edit | env/nginx/conf.d/10-shop.conf, env/nginx/conf.d/20-admin.conf |
| Solution | solutions/LAB01/ (or ./bin/ngx catchup 1) |
flowchart LR
C[curl / browser] -->|Host: shop.localtest.me| E[edge<br/>NGINX]
C -->|Host: admin.localtest.me| E
C -->|any other Host| E
E --> S[/srv/www/shop/]
E --> A[/srv/www/admin/]
E --> D[404 catch-all]
Part A – Start the environment¶
- Prerequisites: Linux VM with Docker Engine + Compose plugin (
docker compose version), ports 80/443/8080 free. - Start everything and check the containers:
*.localtest.meis a public DNS name that always resolves to127.0.0.1– no/etc/hostsediting needed.
✅ Checkpoint: HTTP/1.1 404 with the body PixelShop edge: no site configured for host 'localtest.me'.
💡 No internet DNS in your classroom? Add the names with
tee -a(append!):echo "127.0.0.1 shop.localtest.me admin.localtest.me api.localtest.me" | sudo tee -a /etc/hosts
Get to know the helper – you will use it all the time:
./bin/ngx test # nginx -t
./bin/ngx reload # nginx -t && nginx -s reload (no downtime)
./bin/ngx dump # nginx -T: the whole effective config, includes resolved
Part B – Two websites, one server¶
- Create
env/nginx/conf.d/10-shop.conf:
server {
listen 80;
server_name shop.localtest.me;
root /srv/www/shop;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
- Create
env/nginx/conf.d/20-admin.conf:
server {
listen 80;
server_name admin.localtest.me;
root /srv/www/admin;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Every directive ends with ; – a missing one makes nginx glue the next line onto the directive (invalid number of arguments in "root").
- Reload and test:
./bin/ngx reload
curl -s http://shop.localtest.me/ | grep '<title>'
curl -s http://admin.localtest.me/ | grep '<title>'
curl -s -H "Host: admin.localtest.me" http://127.0.0.1/ | grep '<title>'
✅ Checkpoint: PixelShop - retro gadgets, PixelShop admin, and PixelShop admin again – NGINX picks the server only by the Host header, not by IP.
- What happens for an unknown name?
curl -i http://nobody.localtest.me/→ thedefault_serverin00-default.confanswers. Open that file and explain why.
Part C – Which location wins?¶
Replace the whole content of env/nginx/conf.d/10-shop.conf (the existing location / gets a debug header – two location / blocks would fail with duplicate location "/"). Every block adds an X-Location header, so you can see which one handled a request:
server {
listen 80;
server_name shop.localtest.me;
root /srv/www/shop;
index index.html;
location / {
add_header X-Location "prefix /" always;
try_files $uri $uri/ =404;
}
location = /health {
access_log off;
default_type text/plain;
return 200 "shop OK\n";
}
location ^~ /assets/ {
add_header X-Location "prefix ^~ /assets/" always;
}
location ~* \.(png|jpe?g|gif|svg|ico)$ {
add_header X-Location "regex images" always;
}
location /docs/ {
alias /srv/www/docs/;
add_header X-Location "prefix /docs/ (alias)" always;
}
}
Before you test – predict! Fill in the table, then verify:
for u in /health /assets/logo.svg /img/logo.svg /docs/ /index.html; do
echo -n "$u -> "; curl -sI http://shop.localtest.me$u | grep -i x-location || echo "(no header)"
done
| URI | Your prediction | Result |
|---|---|---|
/health |
||
/assets/logo.svg |
||
/img/logo.svg |
||
/docs/ |
||
/index.html |
Explanation
1. Exact match `=` wins immediately (`/health` – no header, it just returns). 2. Otherwise the **longest prefix** is remembered; if it has `^~`, regexes are skipped (`/assets/logo.svg` → `^~ /assets/`). 3. Regexes are checked **in file order**; first match wins (`/img/logo.svg` → `regex images`). 4. No regex matched → the remembered longest prefix is used (`/docs/`, `/index.html` → `prefix /`).Part D – Custom error page¶
Add these lines inside the shop server { } block (before its closing }) and reload:
✅ Checkpoint: curl -i http://shop.localtest.me/nope returns 404 with the PixelShop page; curl -i http://shop.localtest.me/404.html is also a 404 (internal).
Bonus¶
- Redirect
www.shop.localtest.metoshop.localtest.mewith a separate server block andreturn 301. - Break
aliason purpose: remove the trailing/fromalias /srv/www/docs/;– what does/docs/index.htmlmap to now? (Hint: the error log inenv/logs/error.log.) - Run
./bin/ngx dump | lessand find your server blocks in the effective configuration.
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
nginx: [emerg] unexpected "}" |
missing ; on the line above |
invalid number of arguments in "root" (or any directive) |
missing ; at the end of that line – nginx read the next line as more arguments |
403 Forbidden on / |
no index.html and no autoindex |
| changes are ignored | you forgot ./bin/ngx reload – or the file does not end in .conf |
conflicting server name warning |
the same server_name + port is defined twice |
Files for this lab: Think-Cube/nginx-labs/labs/LAB01-virtual-hosts