Ubuntu 26.04 Tutorial / Build an Ubuntu Server and Complete an Operations Handover
A server build is complete when another administrator can access, change, and recover it. This capstone uses a dedicated Ubuntu 26.04 LTS VM, installs only the required services, and records measured access, restart, and restoration results in a handover document.
Define scope and acceptance criteria
The baseline project provides SSH administration and an Nginx web service within a LAN. Prepare a dedicated VM, console, address plan, permitted management client, and independent backup location. Add public HTTPS only with a controlled domain and validation route; choose Samba or containers when required. Installing every service is not the objective.
| Check | Evidence required for completion |
|---|---|
| Administration | A new key-authenticated session from the allowed client and denial from an excluded route |
| Web service | Expected response for the real host/URL and correct handling of excluded hosts |
| Restart | Addresses, required services, and access rechecked after a planned reboot |
| Backup | Restoration elsewhere with content, permissions, and application behavior checked |
| Operations record | Owner, update/retention policies, change/recovery procedure, and pending items |
Build in dependency order with small changes
- Record release, devices, time, and addressing from the console; complete updates and required reboot.
- Identify the actual Netplan/NetworkManager scope, and use a console, backup, and try verification for address changes.
- Verify key authentication in a new SSH session before tightening policy; inspect actual listeners and service/socket configuration.
- Install required services only; back up, validate, and apply configuration. Restrict actual clients and ports in UFW and external firewalls.
- Prepare independent backup/restoration and failure notifications; retain a baseline before the reboot test.
If a stage fails, investigate its diff and logs before adding another change. Simultaneous port, authentication, and firewall changes obscure the cause of lost access and the correct rollback. Verify that the console recovery route can actually be opened.
Separate local checks from the LAN service
Check the Nginx lesson’s 127.0.0.1:8080 site on the server itself. Mapping alpha.example.test to 192.168.56.20 does not expose its loopback listener. First use --resolve on the server to select the hostname and loopback destination and verify alpha-site. The container lab uses 8081; this LAN exercise uses 8082.
curl --fail --show-error --resolve alpha.example.test:8080:127.0.0.1 http://alpha.example.test:8080/
For LAN checks, prepare lessons 5/15’s host-only network with server .20 and Linux client .10. Add the new site below on the dedicated VM containing lesson 23’s document root. Consistently adapt verified addresses/paths and check port conflicts first. Existing files stop the operation.
(
set -eu
site=/etc/nginx/sites-available/ubuntu-course-lan
enabled=/etc/nginx/sites-enabled/ubuntu-course-lan
sudo test -f /srv/ubuntu-course-alpha/index.html
if ! ip -4 -o address show | grep -Fq ' 192.168.56.20/24 '; then
printf 'Configure the verified server lab address first.\n' >&2; exit 1
fi
for path in "$site" "$enabled"; do
if sudo test -e "$path" || sudo test -L "$path"; then
printf 'Existing lab site: inspect before changing.\n' >&2; exit 1
fi
done
backup_dir=$(sudo mktemp -d /root/nginx-before-lan.XXXXXX)
sudo cp -a /etc/nginx "$backup_dir/"
sudo sh -c 'set -C; cat > "$1"' sh "$site" <<'EOF'
server {
listen 192.168.56.20:8082;
server_name course-lab.example.test;
root /srv/ubuntu-course-alpha;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
EOF
sudo ln -s "$site" "$enabled"
sudo nginx -t
sudo systemctl reload nginx
sudo ss -lntp
printf 'LAN site backup: %s\n' "$backup_dir"
)
Add the rule below with the UFW lesson’s default incoming-deny policy and preserved SSH access. An existing broad 8082 allowance defeats the exclusion test, so review actual rules. Do not forward this host-only port onto the internet.
sudo ufw allow from 192.168.56.10 to 192.168.56.20 port 8082 proto tcp
sudo ufw status numbered
Run curl below from permitted Linux client .10 and verify alpha-site. A new connection from an excluded client, such as host .1, should be blocked by policy. If unknown hostnames must be rejected, design a separate default rejection virtual host; server_name does not replace filtering or authentication.
curl --fail --show-error --connect-timeout 5 --max-time 10 --resolve course-lab.example.test:8082:192.168.56.20 http://course-lab.example.test:8082/
For cleanup, identify and remove only this site and its UFW rule, then validate and reload Nginx. Preserve the loopback site and other services. After reboot, remeasure addresses, actual listeners, and permitted/excluded connections.
Create a small HTTP health check
Save the complete script below as ~/check-web-lab.sh. It requires Bash/curl and takes the actual URL and expected text. For lesson 23’s local check, map alpha.example.test to 127.0.0.1 in the server’s own /etc/hosts, then run bash ~/check-web-lab.sh http://alpha.example.test:8080/ alpha-site. The earlier --resolve affects one curl invocation, not this script’s name resolution.
set -eu
if [ "$#" -ne 2 ]; then
printf "Usage: bash check-web-lab.sh URL EXPECTED_TEXT\n" >&2
exit 2
fi
url=$1
expected=$2
case "$url" in
http://*|https://*) ;;
*) printf "Use an HTTP or HTTPS URL.\n" >&2; exit 2 ;;
esac
if [ -z "$expected" ]; then
printf "Expected text must not be empty.\n" >&2
exit 2
fi
body_file=$(mktemp)
trap 'rm -f -- "$body_file"' EXIT
if ! curl --fail --silent --show-error --connect-timeout 5 --max-time 10 "$url" -o "$body_file"; then
printf "FAIL: request or TLS verification failed\n" >&2
exit 1
fi
if ! grep -Fq -- "$expected" "$body_file"; then
printf "FAIL: expected text missing\n" >&2
exit 1
fi
printf "PASS: %s %s\n" "$(date -u +%FT%TZ)" "$url"
Success returns exit code 0 and PASS; request, TLS, or body failure returns 1, and invalid arguments return 2. Test a deliberately absent expected string to verify failure detection. An HTTP check does not replace database-write, login, or backup checks. Keep credentials out of the URL and separately arrange collection of execution times and exit status.
For the LAN health check, map course-lab.example.test to 192.168.56.20 in client .10’s /etc/hosts or controlled DNS, then run bash ~/check-web-lab.sh http://course-lab.example.test:8082/ alpha-site. Preserve hosts before editing and avoid duplicate name entries. Distinguish local, LAN, and public HTTPS URLs, and expected rejection from service failure.
Record allow, deny, reboot, and restoration tests
Test from actual allowed and excluded clients as well as the server itself. Retain the existing SSH connection while opening a new one, then schedule and verify a reboot. Restore backups into a separate empty location and check content, permissions, and application-specific behavior. Leave unperformed checks pending.
Use Ubuntu’s firewall guide alongside records of actual rules and connection tests.
Fill the handover with measured values
Use this template as a starting point for an operations record. Replace brackets with observed values and reference an approved secret store instead of copying secrets. Assign ownership and recovery responsibility; do not present example addresses or assumed success as measurements.
System: [hostname, purpose, owner]
Platform: [Ubuntu release, kernel, VM host, console route]
Network: [interface, renderer, address, gateway, DNS]
Access: [allowed clients, actual SSH listener, key custody location]
Services: [versions, configuration paths, validation/reload commands]
Exposure: [listeners, UFW rules, external firewall, container bindings]
TLS: [hostnames, renewal mechanism, expiry/failure monitoring]
Backup: [scope, independent destination identity, retention, key custody]
Restore test: [time, destination, content/permission/app results]
Acceptance: [test, client, expected/actual result, evidence location]
Rollback: [change boundary, preserved baseline, restoration procedure]
Pending: [unperformed checks, unresolved risks, owner, target date]
Keep verification evidence with the change history and ask whether another administrator can prepare access, inspection, and recovery from the record alone. Named notification recipients, update/certificate/backup review intervals, and owners for pending work make continued operation independent of the original builder’s memory.




