Skip to content

Debian & Ubuntu — Street Ops

Operational patterns for Debian/Ubuntu systems in production.


Day-One Server Setup (Ubuntu)

# 1. Update everything
sudo apt update && sudo apt upgrade -y

# 2. Set hostname
sudo hostnamectl set-hostname web01.example.com

# 3. Create ops user
sudo adduser deploy
sudo usermod -aG sudo deploy

# 4. SSH hardening
sudo sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd

# 5. Enable firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw --force enable

# 6. Enable unattended security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades

> **One-liner:** Unattended-upgrades is the single most important package on any internet-facing Ubuntu server. It patches known CVEs automatically. Without it, your server is vulnerable to every security advisory published after your last manual update.

# 7. Install essentials
sudo apt install -y curl wget git vim htop tmux net-tools

Emergency Package Recovery

# Fix broken dependencies
sudo apt --fix-broken install

# Reconfigure a broken package
sudo dpkg --configure -a

# Force reinstall a package
sudo apt install --reinstall nginx

# Remove and reinstall cleanly
sudo apt purge nginx
sudo apt install nginx

# Fix missing GPG keys
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys <KEY_ID>

> **Gotcha:** `apt-key` is deprecated since Ubuntu 22.04. Third-party repo keys should go in `/etc/apt/keyrings/` as individual `.gpg` files, referenced by `signed-by=` in the sources list. Old guides that use `apt-key add` will trigger deprecation warnings and will stop working in future releases.

# Clear apt cache (free disk space)
sudo apt clean          # remove all cached .deb files
sudo apt autoclean      # remove only obsolete cached .deb files

# Find package that owns a missing file
dpkg -S /path/to/file
apt-file search /path/to/file    # (needs apt-file installed)

Debug clue: If apt update hangs or fails with "Could not resolve" errors, check /etc/resolv.conf first. On cloud VMs, DHCP lease renewal can silently blank the nameserver entries. Also check if systemd-resolved is running — on Ubuntu 18.04+, /etc/resolv.conf is a symlink to the stub resolver at 127.0.0.53.


APT Security Audit

# Check for available security updates
apt list --upgradable 2>/dev/null | grep -i security

# List packages from a specific repo
apt list --installed 2>/dev/null | grep third-party-repo

# Check package integrity
debsums -c    # list changed config files
debsums -e    # check only config files

# Audit installed packages for CVEs
apt install debsecan
debsecan --suite bookworm

# View package changelogs (check what changed)
apt changelog nginx

Multi-Version Management

# Install specific version
sudo apt install nginx=1.24.0-1ubuntu1

# Hold at current version
sudo apt-mark hold nginx

# Show available versions
apt-cache policy nginx
apt-cache madison nginx

# Show reverse dependencies (what depends on this package)
apt-cache rdepends nginx

# Simulate upgrade to see what would change
apt upgrade --simulate

Under the hood: apt-mark hold writes to /var/lib/apt/extended_states and /var/lib/dpkg/status. A held package is skipped by apt upgrade but can still be explicitly installed with apt install nginx=<version>. Use apt-mark showhold to list all held packages — forgotten holds are a common reason a package stays at an old version for months.


Networking Operations

Netplan Quick Patterns

# Static IP
# /etc/netplan/01-static.yaml
network:
  version: 2
  ethernets:
    ens160:
      addresses: [192.168.1.100/24]
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses: [8.8.8.8, 1.1.1.1]
# VLAN
network:
  version: 2
  ethernets:
    ens160:
      dhcp4: false
  vlans:
    vlan100:
      id: 100
      link: ens160
      addresses: [10.10.100.5/24]
# Bond
network:
  version: 2
  ethernets:
    ens160: {}
    ens192: {}
  bonds:
    bond0:
      interfaces: [ens160, ens192]
      parameters:
        mode: 802.3ad
        lacp-rate: fast
      addresses: [192.168.1.100/24]
# Always test before applying
sudo netplan try    # auto-reverts in 120s
# Then if good:
sudo netplan apply

> **Remember:** Always use `netplan try` before `netplan apply` on remote servers. `try` auto-reverts after 120 seconds if you don't confirm. If you use `apply` and your config has a typo, you lose SSH access and need console access to fix it.

UFW Patterns

# Web server
sudo ufw allow 'Nginx Full'    # 80 + 443

# Database (internal only)
sudo ufw allow from 10.0.0.0/8 to any port 5432

# Rate limit SSH (prevent brute force)
sudo ufw limit ssh

# Logging
sudo ufw logging on
sudo ufw logging medium    # low/medium/high/full

# Check rules with numbers (for deletion)
sudo ufw status numbered

AppArmor Operations

# Quick status
sudo aa-status

# Debug: put profile in complain mode
sudo aa-complain /usr/sbin/nginx

# Test your app, then check logs
sudo journalctl | grep apparmor | tail -20

# Generate fixes from logs
sudo aa-logprof

# Back to enforce
sudo aa-enforce /usr/sbin/nginx

# Create a new profile interactively
sudo aa-genprof /usr/local/bin/myapp
# (run myapp in another terminal, then scan logs)

Kernel Management

# List installed kernels
dpkg -l | grep linux-image

# Check current kernel
uname -r

# Install HWE kernel (Ubuntu LTS)
sudo apt install linux-generic-hwe-22.04

# Remove old kernels (careful!)
sudo apt autoremove --purge

> **Default trap:** `apt autoremove` removes old kernels, but if something goes wrong with the current kernel after reboot, you lose your fallback. Always keep at least one previous kernel installed. Use `apt-mark hold` on the previous kernel before running autoremove.

# Pin a specific kernel version
sudo apt-mark hold linux-image-$(uname -r)
sudo apt-mark hold linux-headers-$(uname -r)

# View GRUB menu entries
grep menuentry /boot/grub/grub.cfg

# Set default kernel
sudo vim /etc/default/grub    # GRUB_DEFAULT=
sudo update-grub

Debian-Specific Patterns

# Check Debian release info
cat /etc/debian_version
lsb_release -a

# Switch to a new Debian release (careful!)
# 1. Update sources.list to new codename
sudo sed -i 's/bullseye/bookworm/g' /etc/apt/sources.list
# 2. Update and full-upgrade
sudo apt update
sudo apt full-upgrade
# 3. Reboot
sudo reboot

# Use backports (newer packages on stable)
echo "deb http://deb.debian.org/debian bookworm-backports main" | \
  sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -t bookworm-backports <package>

Gotcha: When upgrading between Debian major versions (e.g., bullseye to bookworm), always use apt full-upgrade, not apt upgrade. full-upgrade can remove packages when needed to resolve dependency changes. upgrade refuses to remove anything, which can leave your system in a broken half-upgraded state.