DEV Community

Automate It All
Automate It All

Posted on

From a Bare Debian Box to a Public HTTPS Page, in Eight Command Blocks

Lesson one of twelve, written out below. The video is free on YouTube: https://www.youtube.com/watch?v=Yzty9buG4dc

You need a Debian 12 box you can SSH into as root the first time, and a domain whose A record already points at it. Everything below is what the recording shows, in the order it shows it. SITE is your domain, and ADMIN is the human account you will use from now on.

1. One Apt Call

apt-get update -qq
apt-get install -y -qq --no-install-recommends \
  ca-certificates curl debian-keyring debian-archive-keyring apt-transport-https \
  gnupg sudo openssh-server ufw unattended-upgrades
Enter fullscreen mode Exit fullscreen mode

The Caddy repo needs the keyring and transport packages, so they go in here rather than in step 6.

2. A Human Account With Sudo

adduser --disabled-password --gecos "" "$ADMIN"
usermod -aG sudo "$ADMIN"
install -d -m 700 -o "$ADMIN" -g "$ADMIN" "/home/$ADMIN/.ssh"
Enter fullscreen mode Exit fullscreen mode

From your laptop, ssh-copy-id $ADMIN@$SITE puts your public key in place. Do that before step 3, because step 3 is what closes the root door behind you.

3. Root Login Off, and a Parse Check

cat > /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
EOF
/usr/sbin/sshd -t
Enter fullscreen mode Exit fullscreen mode

sshd -t parses the whole config and exits non-zero on a typo. Run it before you restart sshd, because a config that fails to parse after a restart locks you out of a box whose only other door you just shut.

4. A Firewall With Three Doors

ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
ufw show added
Enter fullscreen mode Exit fullscreen mode

A container has no netfilter, so the lesson's script checks /proc/sys/net/ipv4/ip_forward for write access and prints the enable line instead of running it. On your VPS it runs.

5. Security Updates Install Themselves

cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF
unattended-upgrade --dry-run --debug 2>&1 | grep -m1 "Allowed origins are"
Enter fullscreen mode Exit fullscreen mode

The dry run prints the origins it would pull from. Reading that line is how you learn the security pocket is in the list rather than assuming it.

6. Caddy From Its Own Repo

curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key \
  | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt \
  > /etc/apt/sources.list.d/caddy-stable.list
apt-get update -qq && apt-get install -y -qq caddy
Enter fullscreen mode Exit fullscreen mode

Debian's own caddy package lags the upstream release, and the automatic HTTPS behaviour below is the part that moves.

7. Three Lines of Web Server

install -d -m 755 /var/www/site
echo "<h1>$SITE is live</h1>" > /var/www/site/index.html
cat > /etc/caddy/Caddyfile <<EOF
$SITE {
    root * /var/www/site
    file_server
}
EOF
caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile
Enter fullscreen mode Exit fullscreen mode

Caddy reads the site address, asks Let's Encrypt for a certificate, and renews it. There is no certbot step and no cron entry, which is the whole reason this course uses Caddy rather than nginx.

Testing on localhost adds one line, tls internal, so Caddy signs with its own CA rather than failing an ACME challenge for a name nobody can resolve.

8. Read the Page Back

caddy start --config /etc/caddy/Caddyfile
for _ in $(seq 30); do curl -sk "https://$SITE" -o /tmp/page.html && break; sleep 1; done
grep -q "$SITE is live" /tmp/page.html
Enter fullscreen mode Exit fullscreen mode

The loop exists because the first request arrives while Caddy is still finishing its ACME challenge. Thirty one-second tries cover that.

At this point a stranger loads your page over HTTPS, root cannot log in over SSH, ufw allows 22, 80 and 443 only, and security updates install themselves.

Why the Script Is the Test

Every lesson in this course ships that commands.sh alongside the video, and the course's test suite runs each one head to tail in a fresh debian:12-slim container. A command that drifts from what the recording shows fails the run. Twelve of twelve were green on 2026-09-09.

The firewall step branches for the same reason. A script claiming a firewall the container never raised would pass a test and teach the wrong thing.

The Other Eleven

Lessons 2 and 3 are free too: Caddy as your web server, then systemd running your own service behind it. Lessons 4 to 12 cover Tailscale as a private network, Tailscale Funnel, Docker Compose, a reverse proxy across three subdomains, an agent as a systemd unit, a Telegram gateway, one bot token per box, restic snapshots restored into an empty directory, and auto-shutdown with monitoring.

The narration is a cloned voice of mine rather than a live read, and the YouTube uploads carry the altered-content disclosure.

Lessons 4 to 12 are 49 EUR as MP4 downloads with a 30-day refund: https://automate-it-all.win/course/

Questions: skills@automate-it-all.win. Author: https://fidel-perez.github.io/portfolio/

Top comments (0)