Production Linux server setup for 2026.
OS choice
- Ubuntu 24.04 LTS: most familiar, biggest community.
- Debian 12: stable, smaller, “boring” in best sense.
- Rocky / Alma 9: RHEL-compatible.
- Fedora: bleeding edge.
- Alpine: minimal, good for containers.
For most: Ubuntu LTS.
Initial setup
# 1. Update
apt update && apt upgrade -y
apt install -y vim git curl wget htop tmux fail2ban ufw \
unattended-upgrades zsh
# 2. Hostname
hostnamectl set-hostname myserver.example.com
# 3. Time
timedatectl set-timezone UTC
timedatectl status
# 4. Create admin user
useradd -m -s /bin/bash -G sudo alice
echo 'alice ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/alice
mkdir -p /home/alice/.ssh
cp ~/.ssh/authorized_keys /home/alice/.ssh/
chown -R alice:alice /home/alice/.ssh
chmod 700 /home/alice/.ssh
# 5. SSH lockdown (see hardening cheatsheet)
# 6. Firewall
ufw allow OpenSSH
ufw allow http
ufw allow https
ufw enable
# 7. fail2ban
systemctl enable --now fail2ban
# 8. Disable root login
passwd -l root
Disk layout (typical)
/boot 1G
/ 30G (root)
/var 50G
/home 10G+
swap 4G (or zram)
/data remaining (LVM)
sysctl
# /etc/sysctl.d/99-server.conf
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
fs.file-max = 2097152
vm.swappiness = 10
kernel.dmesg_restrict = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
ulimit
# /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
* soft nproc 32768
Unattended upgrades
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
Auto-installs security patches.
Time sync
timedatectl set-ntp on
timedatectl status
Monitoring agent
node-exporter (Prometheus)
curl -L https://github.com/prometheus/node_exporter/releases/latest/download/node_exporter-1.8.0.linux-amd64.tar.gz | tar xz
mv node_exporter*/node_exporter /usr/local/bin/
cat > /etc/systemd/system/node_exporter.service <<EOF
[Unit]
Description=Node Exporter
[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now node_exporter
Vector / Promtail (logs)
Ship to Loki.
Backup setup
See backups cheatsheet. Recommended: restic to S3-compatible backend.
0 2 * * * /opt/backup.sh
Docker (optional but common)
curl -fsSL https://get.docker.com | sh
usermod -aG docker alice
systemctl enable --now docker
/etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"live-restore": true
}
TLS automation
apt install certbot
certbot certonly --webroot -w /var/www -d example.com --email [email protected] --agree-tos
Auto-renewal via certbot.timer (installed by default).
Web stack (Caddy alternative)
Caddy = simpler than nginx + auto-TLS:
apt install caddy
cat > /etc/caddy/Caddyfile <<EOF
example.com {
reverse_proxy localhost:8000
}
EOF
systemctl reload caddy
SSH key audit
# Show authorized keys for each user
for u in $(getent passwd | cut -d: -f1); do
f=/home/$u/.ssh/authorized_keys
[ -r "$f" ] && echo "=== $u ===" && cat "$f"
done
.bashrc / .zshrc essentials
alias ll='ls -lah'
alias ..='cd ..'
alias ...='cd ../..'
alias g='git'
alias k='kubectl'
alias d='docker'
alias dcu='docker compose up'
alias dcd='docker compose down'
export EDITOR=vim
export HISTSIZE=10000
export HISTFILESIZE=10000
shopt -s histappend
# Better history
HISTCONTROL=ignoredups:erasedups
PROMPT_COMMAND='history -a'
Useful packages
apt install -y \
htop btop ncdu mtr-tiny \
rsync restic \
jq yq \
ripgrep fd-find bat fzf \
vim neovim tmux \
iotop sysstat dstat \
net-tools dnsutils whois nmap tcpdump \
apt-transport-https ca-certificates \
gpg curl wget
Daily ops checklist
- Disk usage < 80% (
df -h). - No failed services (
systemctl --failed). - No errors in journal (
journalctl -p err --since today). - Cert expiry > 14 days.
- Recent backup verified.
- Memory not swap-thrashing.
- CPU steal time low (cloud VM).
Provisioning
For repeatable: Ansible playbook with all of the above. Or Terraform + cloud-init.
Cloud-init example:
#cloud-config
users:
- name: alice
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAA...
package_update: true
package_upgrade: true
packages:
- fail2ban
- ufw
- htop
runcmd:
- ufw allow OpenSSH
- ufw allow http
- ufw allow https
- ufw enable
- systemctl enable --now fail2ban
Disaster recovery test
Quarterly:
- Spin up new VM.
- Run provisioning.
- Restore latest backup.
- Switch DNS / LB.
- Time the whole thing.
Read this next
That’s 20 Linux cheatsheets. Next category: AI/LLM.
If you want my full server provisioning playbook, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .