Home Lab Build

Raspberry Pi4 Home Server
BIND9 + PostgreSQL + NGINX + FreeRADIUS

This full setup guide builds your home server on Raspberry Pi4 at 10.10.10.250 for network 10.10.10.0/24. You will deploy BIND9, PostgreSQL, NGINX, and FreeRADIUS, make the Pi authoritative for codeandcore.home, and validate hostnames such as iphone.codeandcore.home.

HardwareRaspberry Pi4 (4GB+)
OSRaspberry Pi OS 64-bit
Server IP10.10.10.250
Network10.10.10.0/24
Local Zonecodeandcore.home
DNSBIND9
DatabasePostgreSQL 15/16
WebNGINX
AAAFreeRADIUS 3.x
Teaching Standard

Each lesson is explanation-first: objective, why it matters, exact commands, then verification checkpoints.

Lesson 1

Install Raspberry Pi OS and Base Packages

Objective: prepare a stable Raspberry Pi OS foundation before installing DNS and database services.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches stable host preparation before service installation.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Think of this stage as establishing deterministic runtime conditions. Package drift, missing utilities, and incorrect system time create subtle bugs later in DNS and database behavior, so this lesson is about reducing hidden variables before service deployment starts.

When this preparation is done correctly, every later troubleshooting step becomes clearer because logs are timestamp-accurate, dependency tools are present, and the base OS state is predictable across reboots and updates.

Run

This command block performs the mandatory preflight for reliable service behavior: package refresh, OS upgrades, baseline tools, and timezone alignment for consistent logs and SQL timestamps.

bash
sudo apt-get update
sudo apt-get full-upgrade -y
sudo apt-get install -y curl wget git unzip htop jq dnsutils ca-certificates
sudo timedatectl set-timezone Africa/Kampala
timedatectl status

This lesson teaches base-system readiness for deterministic DNS and database behavior.

You verify no package errors and correct system time settings.

Checkpoint: system updates complete cleanly, tools install without dependency issues, and timedatectl reports the expected timezone.

Lesson 2

Set Hostname and Static IP Identity (10.10.10.250)

Objective: assign predictable identity and fixed addressing so the Pi is always reachable as your DNS authority.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches stable network identity so clients and services can trust one DNS endpoint.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

This block sets host identity and static interface configuration. The key outcome is a permanent resolver endpoint at 10.10.10.250 for the 10.10.10.0/24 network.

bash
example for eth0
sudo hostnamectl set-hostname pi-dns-core
    echo "127.0.1.1 pi-dns-core" | sudo tee -a /etc/hosts

    # Configure static IP in dhcpcd
    sudo tee -a /etc/dhcpcd.conf >/dev/null <<'EOF'
    interface eth0
    static ip_address=10.10.10.250/24
    static routers=10.10.10.10
    static domain_name_servers=10.10.10.250
    EOF

    sudo systemctl restart dhcpcd
    ip -4 addr show eth0
Router Reservation

Add a DHCP reservation for the Pi MAC so 10.10.10.250 is never reused by another device.

You verify hostname resolves locally and server IP remains stable.

Checkpoint: ip -4 addr show eth0 reports 10.10.10.250/24 and the host remains reachable after network restart.

Lesson 3

Install BIND9

Objective: deploy and start the DNS engine that will serve recursive and authoritative requests.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches resolver installation with diagnostic tooling.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

This install sequence brings in the resolver daemon and support tools, then enables the service for startup persistence.

bash
sudo apt-get install -y bind9 bind9utils bind9-doc dnsutils
sudo systemctl enable --now bind9
sudo systemctl status bind9 --no-pager

You verify bind9 service is active and named-checkconf is available.

Checkpoint: BIND9 is active and initial health commands return without fatal errors.

Lesson 4

Configure BIND9 as Home Authoritative + Recursive Resolver

Objective: make the Pi authoritative for codeandcore.home while still resolving internet DNS for clients.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches trust boundaries, recursion behavior, and logging for learning analytics.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Configure named.conf.options

This policy block limits recursion to trusted sources (127.0.0.1 and 10.10.10.0/24), defines upstream forwarders, and hardens transfer/update behavior.

named.conf
/etc/bind/named.conf.options
acl "trusted" {
    127.0.0.1;
    10.10.10.0/24;
};

options {
    directory "/var/cache/bind";
    listen-on { any; };
    listen-on-v6 { any; };
    allow-query { trusted; };
    allow-recursion { trusted; };
    forwarders { 1.1.1.1; 8.8.8.8; 9.9.9.9; };
    forward only;
    recursion yes;
    dnssec-validation auto;
    allow-transfer { none; };
    notify no;
};

Create authoritative local zone

This declaration tells BIND it owns the codeandcore.home namespace and where to read authoritative records.

named.conf
/etc/bind/named.conf.local
zone "codeandcore.home" {
        type master;
        file "/etc/bind/db.codeandcore.home";
        allow-update { none; };
    };

This zone file defines your local naming contract. Example hostnames such as iphone.codeandcore.home are resolved here, not upstream.

dns-zone
/etc/bind/db.codeandcore.home
$TTL 86400
    @   IN  SOA ns1.codeandcore.home. admin.codeandcore.home. (
        2026051201 ; serial
        3600       ; refresh
        1800       ; retry
        1209600    ; expire
        86400 )    ; minimum

    @       IN  NS  ns1.codeandcore.home.
    ns1     IN  A   10.10.10.250
    dns     IN  A   10.10.10.250
    iphone  IN  A   10.10.10.50
    router  IN  A   10.10.10.10
    printer IN  A   10.10.10.40

Set DHCP to use this resolver

Clients must be pointed to 10.10.10.250 by DHCP, otherwise they will bypass your authoritative resolver and local names will fail on client devices.

MikroTik GUI
WinBox or WebFig
1) Open IP -> DHCP Server -> Networks.
    2) Open the 10.10.10.0/24 network entry.
    3) Set Gateway to 10.10.10.10.
    4) Set DNS Servers to 10.10.10.250.
    5) Set Domain to codeandcore.home.
    6) Apply and OK.
    7) Renew DHCP lease on one client and confirm DNS is now 10.10.10.250.

Enable query logging

Logging makes DNS activity observable for verification and analytics. Without this, troubleshooting and usage analysis become guesswork.

bash + named.conf
/etc/bind/named.conf.local
sudo mkdir -p /var/log/named
sudo chown bind:bind /var/log/named

logging {
  channel queries_log {
    file "/var/log/named/queries.log" versions 7 size 200m;
    severity dynamic;
    print-time yes;
  };
  category queries { queries_log; };
  category query-errors { queries_log; };
};

Validate and restart

This validation chain confirms both resolver modes: recursion (internet domains) and authoritative local zone answers.

bash
sudo named-checkconf
sudo systemctl restart bind9
dig @127.0.0.1 google.com A
dig @10.10.10.250 iphone.codeandcore.home A
sudo tail -n 20 /var/log/named/queries.log

You verify both internet recursion and local zone records (for example iphone.codeandcore.home) resolve correctly.

Checkpoint: clients in 10.10.10.0/24 resolve external domains and local hostnames such as iphone.codeandcore.home through 10.10.10.250.

Lesson 5

Install PostgreSQL

Objective: install persistent relational storage for DNS analytics and metadata.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches database runtime installation before schema and analytics setup.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

This block installs PostgreSQL and enables startup persistence, then confirms engine availability with a version query.

bash
sudo apt-get install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
sudo -u postgres psql -c "SELECT version();"

You verify PostgreSQL service is active and version query returns successfully.

Checkpoint: database service stays active and responds to administrative commands.

Lesson 6

Create DNS Analytics Database

Objective: create least-privilege database credentials and dedicated analytics database.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches least-privilege database access for DNS ingestion and reporting.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

This SQL block creates an application user and an isolated database so ingestion and reporting avoid superuser dependence.

sql
sudo -u postgres psql
CREATE USER dns_user WITH PASSWORD 'ChangeThisPassword!'
  NOSUPERUSER NOCREATEDB NOCREATEROLE LOGIN;

CREATE DATABASE dns_analytics OWNER dns_user;
GRANT ALL PRIVILEGES ON DATABASE dns_analytics TO dns_user;
\q

You verify dns_user can connect to dns_analytics using password authentication.

Checkpoint: dns_user authenticates and database ownership/permissions are correct.

Lesson 7

Make the Server Use Itself as DNS

Objective: force host-level lookups on the Pi to use local BIND for consistent resolver behavior.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches host-level resolver redirection to local BIND on loopback.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

The core concept is path control. If the host itself does not resolve through the same DNS path you designed for clients, behavior diverges and diagnostics become misleading. Redirecting to loopback aligns local and network-facing lookup behavior.

This also teaches verification discipline: first prove BIND answers locally, then change resolver plumbing, then confirm queries appear in logs. That order avoids masking resolver misconfiguration behind operating-system DNS defaults.

Run

This sequence first verifies local resolver answers, then rewires systemd-resolved to loopback and confirms both resolver status and query logging.

bash
dig @127.0.0.1 google.com A

sudo cp /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak
sudo tee /etc/systemd/resolved.conf >/dev/null <<'EOF'
[Resolve]
DNS=127.0.0.1
FallbackDNS=
Domains=~.
EOF

sudo systemctl restart systemd-resolved
resolvectl status
dig google.com A
sudo tail -n 20 /var/log/named/queries.log

You verify resolvectl shows DNS=127.0.0.1 and host queries appear in BIND logs.

Checkpoint: host DNS path is local-first and still resolves both internet and local domains successfully.

Lesson 8

Final Stack Validation

Objective: verify resolver, authoritative zone, and database operate correctly together after configuration.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches final proof that resolver and database are healthy on the Pi.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

This final test confirms service health, recursion, local authority answers, and database responsiveness in one runbook.

bash
sudo systemctl status bind9 --no-pager
sudo systemctl status postgresql --no-pager
dig @127.0.0.1 cloudflare.com A
    dig @10.10.10.250 iphone.codeandcore.home A
psql -U dns_user -d dns_analytics -h localhost -c "SELECT NOW();"
Completion Standard

You are complete when clients in 10.10.10.0/24 use 10.10.10.250 as DNS, local names like iphone.codeandcore.home resolve, and PostgreSQL is healthy after reboot.

Lesson 9

Fast Internet Blueprint for Your Home

Objective: optimize perceived speed for YouTube, Prime Video, Netflix, and large device updates across your whole home.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches what actually improves speed in 2026 for encrypted streaming traffic.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

The performance model here is layered. DNS efficiency improves startup and connection setup, queue discipline preserves responsiveness under contention, and selective caching helps only where protocols allow reuse. Mixing those levers correctly is what users feel as "fast internet" in real homes.

This lesson also corrects a common misconception: throughput alone is not quality. Stability under concurrent family traffic is usually a latency and queueing problem, not a raw bandwidth problem.

Reality Check

Traditional generic web caching proxies are mostly low-value for modern HTTPS/QUIC streaming. The biggest wins are DNS cache quality, good Wi-Fi/wired backhaul, and SQM/QoS on the router.

Checkpoint: peak-time streaming remains stable, app launch DNS lookups are faster, and updates no longer saturate the whole network.

Lesson 10

Tune BIND9 for Better Cache Behavior

Objective: reduce repeat lookup latency and improve resolver resilience under family traffic bursts.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches cache tuning that improves responsiveness without breaking correctness.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

Apply these options inside /etc/bind/named.conf.options in your existing options { ... } block, then reload BIND.

named.conf
cache tuning
options {
    directory "/var/cache/bind";
    listen-on { any; };
    allow-query { trusted; };
    allow-recursion { trusted; };

    forwarders { 1.1.1.1; 8.8.8.8; 9.9.9.9; };
    forward only;

    dnssec-validation auto;
    max-cache-size 256m;
    min-cache-ttl 60;
    max-cache-ttl 86400;
    max-ncache-ttl 300;
    serve-stale yes;
    stale-answer-enable yes;

    allow-transfer { none; };
    notify no;
};
bash
sudo named-checkconf
sudo systemctl restart bind9
for i in 1 2 3; do dig @10.10.10.250 youtube.com A +stats | grep "Query time"; done

This lesson teaches practical cache tuning for lower repeat-query latency and graceful stale serving.

You verify second/third query times are lower and resolver remains responsive during upstream hiccups.

Checkpoint: repeated domain lookups are faster and no resolver instability appears after restart.

Lesson 11

Use Caching Where It Still Delivers Value

Objective: deploy caching only for traffic classes that still benefit in modern encrypted ecosystems.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches selective caching strategy, not one-size-fits-all proxying.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Selective caching is a cost-benefit decision. Modern encrypted streaming generally prevents useful intermediary reuse, so generic proxy caching adds complexity without proportional gain. Update channels and package repositories remain the practical high-value targets.

The operational lesson is to measure before expanding scope. Start with one cacheable class, confirm measurable savings, then decide if additional layers are worth maintenance overhead.

Apt cache for Linux devices (high value if multiple Debian/Ubuntu clients)

This setup is worth running when many Linux clients repeat package downloads. It does not accelerate encrypted video streams, but it cuts repeated package bandwidth immediately.

bash
on 10.10.10.250
sudo apt-get install -y apt-cacher-ng
sudo systemctl enable --now apt-cacher-ng
sudo systemctl status apt-cacher-ng --no-pager
bash
on Ubuntu/Debian clients
echo 'Acquire::http::Proxy "http://10.10.10.250:3142";' | sudo tee /etc/apt/apt.conf.d/01proxy
sudo apt-get update

Windows and Apple updates without Squid complexity

For non-Linux homes, modern update optimization is better done by platform-native mechanisms instead of a generic proxy cache. These methods are aware of signed content distribution rules and are more reliable than transparent proxy tricks.

checklist
Windows path: enable Delivery Optimization LAN sharing on all Windows PCs.
    Apple path: enable Content Caching on an always-on Mac where available.
    Android and TV path: rely on scheduled update windows plus QoS rather than proxy caching.
    Policy decision: keep Squid optional for lab validation, not as a core streaming accelerator.
Streaming Reality

YouTube, Prime Video, and Netflix are typically encrypted end-to-end with dynamic manifests and DRM. Generic caching proxies usually provide little to no benefit for that content.

Squid Decision

For your home scenario, Squid is not a primary speed tool anymore. Keep it out of the critical path unless you have a narrow, measured use case that proves benefit.

You verify apt-cacher-ng hit counters increase when multiple clients update packages.

Checkpoint: update bandwidth drops for repeated Linux package downloads while streaming behavior remains unchanged.

Lesson 12

Configure SQM/QoS for Real-World Speed Feel

Objective: eliminate bufferbloat so streams stay smooth and interactive traffic stays responsive during heavy downloads.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches that queue discipline usually beats caching proxy tricks for family streaming homes.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run (router side)

Apply Smart Queue Management on your WAN bottleneck at about 90-95% of real line speed. Keep FastTrack disabled for traffic that must be shaped, otherwise queue control is bypassed and latency spikes return under load.

checklist
Measure real WAN throughput (down and up) during quiet time.
    Set SQM bandwidth caps to approximately 90-95% of measured rates.
    Enable CAKE or FQ_CoDel according to platform support.
    Prioritize latency-sensitive classes over bulk download classes.
    Re-test while one device streams and another performs large updates.
RB2011 CPU Reality

The RB2011 can struggle when advanced shaping is pushed near high WAN rates. Shape below line rate first, then increase carefully while watching CPU load and latency.

This lesson teaches queue control as the main lever for perceived speed under contention.

You verify stable stream quality and lower latency during concurrent downloads.

Checkpoint: family streaming remains smooth while updates/downloads run in parallel, with no severe latency spikes.

Lesson 13

MikroTik RB2011 Production Profile (10.10.10.0/24)

Objective: apply a practical RB2011-specific baseline that enforces your Pi DNS at 10.10.10.250 and improves stream stability during updates.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches RB2011 controls that matter most: DHCP DNS policy, DNS hijack prevention, and conservative SQM.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

The goal is policy enforcement, not just configuration. DHCP advertisement sets the preferred DNS path, while NAT redirect rules enforce it for misconfigured clients. Together, those controls keep local-zone behavior consistent across your LAN.

The second lesson is capacity realism. RB2011 queueing can improve user experience significantly, but only when shaping targets are chosen with CPU limits in mind and validated under concurrent traffic.

Step 1: Enforce your DNS resolver in DHCP and NAT

This block makes the RB2011 advertise your Pi DNS and transparently redirects LAN DNS requests to 10.10.10.250 so client bypass attempts do not break local zone behavior.

MikroTik GUI
WinBox or WebFig
DHCP policy:
    1) Open IP -> DHCP Server -> Networks.
    2) Edit the 10.10.10.0/24 network.
    3) Set DNS Servers to 10.10.10.250.
    4) Set Domain to codeandcore.home.

    DNS redirect policy:
    1) Open IP -> Firewall -> NAT.
    2) Add a new dstnat rule for UDP port 53 from src-address 10.10.10.0/24.
    3) Set action to dst-nat and To Addresses to 10.10.10.250.
    4) Add a second dstnat rule with the same settings for TCP port 53.
    5) Place both DNS redirect rules above broad accept/fasttrack-style rules.
    6) Apply and test from a client that previously used external DNS.

Step 2: Apply RB2011-friendly queue shaping

Start conservative to protect latency. Set limits around 85-90% of measured line rate, then tune upward only if CPU and latency stay healthy. Disable FastTrack for flows that must be shaped.

MikroTik GUI
queue setup via interface panels
1) Run 3 speed tests at quiet time and write down stable down/up rates.
    2) Open IP -> Firewall -> Filter Rules and disable FastTrack for traffic you want shaped.
    3) Open Queues and create your WAN shaping policy for 10.10.10.0/24.
    4) Set max limits to around 85-90% of measured down/up rates.
    5) Choose fq-codel where available; otherwise use PCQ as fallback.
    6) Apply changes and run a stream + update test to confirm lower latency spikes.
RouterOS Compatibility

If fq-codel is not available on your firmware, update RouterOS first. If you cannot update immediately, use PCQ queue types as a temporary fallback and keep conservative max-limit values.

MikroTik GUI
temporary fallback profile
PCQ fallback steps:
    1) Open Queues -> Queue Types.
    2) Create one PCQ type for download (classifier: destination address).
    3) Create one PCQ type for upload (classifier: source address).
    4) Open Queues -> Simple Queues and create a queue for target 10.10.10.0/24.
    5) Set max limits to conservative values (about 85-90% of line rate).
    6) Assign PCQ upload/download types to the queue and retest under load.

Step 3: Verify under real family load

Validation should happen while one device streams and another runs updates. If video quality is stable and browsing latency stays responsive, your policy is working.

checklist
Confirm the client DNS server is 10.10.10.250.
    Run simultaneous Netflix or YouTube streaming plus a large OS update.
    Ping 1.1.1.1 from a client during load and monitor latency spread.
    If latency spikes significantly, lower max-limit values and retest.
    If stable and CPU headroom exists, raise caps in small controlled steps.
High-Impact Hardware Note

RB2011 built-in Wi-Fi is an old 2.4 GHz radio. For modern streaming quality, use a dedicated dual-band AP (AC/AX) on Ethernet and keep RB2011 focused on routing/QoS.

This lesson teaches a production-ready RB2011 baseline tailored to your 10.10.10.0/24 network.

You verify DNS policy enforcement and stable stream quality during parallel update traffic.

Checkpoint: all clients resolve through 10.10.10.250, local names stay reliable, and streaming remains smooth during heavy background updates.

Lesson 14

Install NGINX and Publish the Service

Objective: install NGINX as your home web front-end and expose it safely on your LAN.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches clean web service installation, startup persistence, and first health validation.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

This lesson frames NGINX as infrastructure entrypoint rather than a simple web page host. You are establishing a front-end service that can terminate TLS, proxy internal apps, and provide a stable access surface for future services.

Health validation at this stage should include both service state and socket exposure so you know the daemon is not only running but actually reachable from the network context where clients will consume it.

Run

This block installs NGINX, enables boot-time startup, and validates that it is listening before you build custom site definitions.

bash
sudo apt-get install -y nginx
sudo systemctl enable --now nginx
sudo systemctl status nginx --no-pager
ss -tulpn | grep -E ':80|:443'

You verify NGINX is active and listening on HTTP/HTTPS sockets.

Checkpoint: browsing to http://10.10.10.250 from LAN returns the default NGINX page.

Lesson 15

Build Your NGINX Home Site and Reverse Proxy

Objective: create a maintainable NGINX site profile for local domains and internal app fronting.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches virtual host structure, static home portal serving, and reverse proxy routing patterns.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Create site content and server block

This configuration gives you a primary site at home.codeandcore.home and an example reverse proxy endpoint for a local app running on port 3000.

bash + nginx.conf
/etc/nginx/sites-available/home-core
sudo mkdir -p /var/www/home-core
cat <<'EOF' | sudo tee /var/www/home-core/index.html >/dev/null
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Home Core</title></head>
<body><h1>home.codeandcore.home</h1><p>NGINX is live.</p></body>
</html>
EOF

cat <<'EOF' | sudo tee /etc/nginx/sites-available/home-core >/dev/null
server {
    listen 80;
    server_name home.codeandcore.home;

    root /var/www/home-core;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location /app/ {
        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;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/home-core /etc/nginx/sites-enabled/home-core
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

This lesson teaches how to host your own local site and proxy internal services behind one NGINX endpoint.

You verify home.codeandcore.home resolves and serves your custom page from NGINX.

Checkpoint: http://home.codeandcore.home opens your custom page and /app/ forwards to your local app if it is running.

Lesson 16

Add TLS and Baseline NGINX Hardening

Objective: protect local credentials and admin pages by serving HTTPS with sane defaults.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches local-certificate creation, HTTPS virtual hosts, and minimum hardening headers.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Create local certificate and HTTPS server block

This block generates a private certificate for your local domain and upgrades NGINX from plain HTTP-only to HTTPS-first serving.

bash + nginx.conf
local TLS profile
sudo mkdir -p /etc/nginx/certs
sudo openssl req -x509 -nodes -newkey rsa:4096 -days 825 \
  -keyout /etc/nginx/certs/home-core.key \
  -out /etc/nginx/certs/home-core.crt \
  -subj "/CN=home.codeandcore.home"

cat <<'EOF' | sudo tee /etc/nginx/sites-available/home-core >/dev/null
server {
    listen 80;
    server_name home.codeandcore.home;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name home.codeandcore.home;

    ssl_certificate /etc/nginx/certs/home-core.crt;
    ssl_certificate_key /etc/nginx/certs/home-core.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy no-referrer-when-downgrade always;

    root /var/www/home-core;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location /app/ {
        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;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF

sudo nginx -t
sudo systemctl reload nginx
Trust Warning

Self-signed certs are expected to warn in browsers until you import your local CA/certificate into trusted stores.

Checkpoint: https://home.codeandcore.home loads and HTTP redirects automatically to HTTPS.

Lesson 17

Install FreeRADIUS and Enable Core Modules

Objective: deploy FreeRADIUS as your AAA backend for MikroTik authentication and accounting.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches FreeRADIUS package deployment, service startup, and module activation for practical home-lab AAA.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run

This command sequence installs the server, utilities, and SQL support package, then confirms runtime status.

bash
sudo apt-get install -y freeradius freeradius-utils freeradius-postgresql
sudo systemctl enable --now freeradius
sudo systemctl status freeradius --no-pager
sudo freeradius -CX

You verify FreeRADIUS starts cleanly and config sanity check passes.

Checkpoint: FreeRADIUS service is active with no syntax errors from freeradius -CX.

Lesson 18

Configure FreeRADIUS Clients, Users, and SQL Logging

Objective: define MikroTik as a trusted RADIUS client, create test users, and store accounting data in PostgreSQL.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches identity policy in FreeRADIUS and how to align AAA data with your existing PostgreSQL stack.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Define MikroTik client and local test identities

This configuration tells FreeRADIUS which NAS can send requests and creates a starter user policy for authentication testing.

freeradius conf
clients.conf + users file
sudo cp /etc/freeradius/3.0/clients.conf /etc/freeradius/3.0/clients.conf.bak
cat <<'EOF' | sudo tee /etc/freeradius/3.0/clients.d/mikrotik-rb2011.conf >/dev/null
client rb2011 {
    ipaddr = 10.10.10.10
    secret = ChangeThisRadiusSecret!
    shortname = rb2011
    nastype = mikrotik
}
EOF

sudo cp /etc/freeradius/3.0/mods-config/files/authorize /etc/freeradius/3.0/mods-config/files/authorize.bak
cat <<'EOF' | sudo tee -a /etc/freeradius/3.0/mods-config/files/authorize >/dev/null
homeadmin Cleartext-Password := "ChangeThisStrongPassword!"
    Reply-Message := "Welcome %{User-Name}"
EOF

Enable PostgreSQL accounting

These steps connect FreeRADIUS to your existing PostgreSQL instance so accounting events are queryable with your analytics workflows.

bash + sql
radius schema + module wiring
sudo -u postgres psql -c "CREATE DATABASE radius OWNER dns_user;"
sudo -u postgres psql radius -f /etc/freeradius/3.0/mods-config/sql/main/postgresql/schema.sql

sudo cp /etc/freeradius/3.0/mods-available/sql /etc/freeradius/3.0/mods-available/sql.bak
sudo sed -i 's/dialect = "sqlite"/dialect = "postgresql"/' /etc/freeradius/3.0/mods-available/sql
sudo sed -i 's#driver = "rlm_sql_${dialect}"#driver = "rlm_sql_${dialect}"#' /etc/freeradius/3.0/mods-available/sql
sudo sed -i 's/server = "localhost"/server = "127.0.0.1"/' /etc/freeradius/3.0/mods-available/sql
sudo sed -i 's/login = "radius"/login = "dns_user"/' /etc/freeradius/3.0/mods-available/sql
sudo sed -i 's/password = "radpass"/password = "ChangeThisPassword!"/' /etc/freeradius/3.0/mods-available/sql
sudo sed -i 's/radius_db = "radius"/radius_db = "radius"/' /etc/freeradius/3.0/mods-available/sql

sudo ln -sf /etc/freeradius/3.0/mods-available/sql /etc/freeradius/3.0/mods-enabled/sql
sudo systemctl restart freeradius
sudo systemctl status freeradius --no-pager
Password Hygiene

Rotate all placeholder secrets immediately: RADIUS shared secret, test user password, and SQL password entries.

Checkpoint: FreeRADIUS starts with SQL module enabled and no module-load failures in journal logs.

Lesson 19

Connect RB2011 to FreeRADIUS Using GUI Only

Objective: link MikroTik authentication to your Pi RADIUS server without RouterOS CLI syntax.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches WinBox/WebFig navigation for RADIUS server registration and service binding.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

MikroTik GUI flow

Use this procedure to register your RADIUS server and assign it to services such as Hotspot, PPP, Login, or Wireless security depending on your deployment.

MikroTik GUI
WinBox or WebFig
1) Open Radius from the left menu.
2) Add a new server entry.
3) Address: 10.10.10.250.
4) Secret: same value as FreeRADIUS client secret.
5) Authentication Port: 1812, Accounting Port: 1813.
6) Timeout: start with 300ms to 600ms on LAN.
7) Check services you need: Login, Hotspot, PPP, Wireless.
8) Apply and save.

If using Wi-Fi enterprise profile in MikroTik GUI:
1) Open Wireless -> Security Profiles.
2) Edit your enterprise profile.
3) Set authentication to WPA2-EAP or WPA3-EAP (if supported).
4) Enable RADIUS MAC/Auth as required by your design.
5) Select the Radius server entry you just created.
6) Apply and reconnect one test client.

You verify MikroTik shows Access-Accept replies and successful accounting starts in logs.

Checkpoint: a test account authenticates through RB2011 against FreeRADIUS on 10.10.10.250.

Lesson 20

End-to-End Validation and Operations Runbook

Objective: prove NGINX and FreeRADIUS are production-usable and survive reboot with clear troubleshooting paths.

Learning Focus: This lesson builds practical engineering judgment, not just task completion. As you run each step, connect the action to runtime behavior, failure signals, and design trade-offs so you can adapt the pattern in real systems.

Teaching Lens

This lesson teaches repeatable validation for web, AAA, and log visibility so changes are safe over time.

This lesson also focuses on operational reasoning: what healthy behavior looks like, what failure signals look like, and how this step protects the reliability of the lessons that come next.

Run full validation

This runbook confirms service health, local HTTPS response, RADIUS authentication, and SQL accounting availability in one pass.

bash
sudo systemctl status nginx --no-pager
sudo systemctl status freeradius --no-pager
curl -kI https://home.codeandcore.home
radtest homeadmin ChangeThisStrongPassword! 127.0.0.1 0 ChangeThisRadiusSecret!
sudo journalctl -u freeradius -n 80 --no-pager
psql -U dns_user -d radius -h localhost -c "SELECT COUNT(*) FROM radacct;"
Completion Standard

You are complete when NGINX serves your local HTTPS site, RADIUS test authentication succeeds, MikroTik GUI service binding works, and accounting rows appear in PostgreSQL.

Troubleshooting

Common Issues on Raspberry Pi4

bash
# BIND9 syntax and logs
sudo named-checkconf
sudo journalctl -u bind9 -n 50

# PostgreSQL logs
sudo journalctl -u postgresql -n 50

# Resolver path
resolvectl status
cat /etc/resolv.conf
If DNS Fails After Reboot

Check service ordering and ensure both bind9 and systemd-resolved are enabled. If needed, restart in this order: bind9, then systemd-resolved.