Fresh Server, Trouble Before You Even Start
When I first switched from CentOS to Ubuntu, my habit was to deploy code immediately after installation — skipping every configuration step. Three days later, the server got brute-forced via SSH, logs were flooded with error messages, and the lack of a swap file caused a Node.js process to get killed by the OOM killer at 2 AM.
It took about a week to get used to Ubuntu’s package management after leaving CentOS. But the more painful part was losing an entire morning restoring services from backup — for mistakes that were completely avoidable with 20 minutes of upfront configuration.
This post is the checklist I run through every time I install Ubuntu Server 22.04. Nothing fancy — just lessons I’ve paid for with real production incidents.
Why Are Freshly Installed Servers Prone to Trouble?
A fresh Ubuntu Server 22.04 install looks fine on the surface, but there are a few genuinely dangerous defaults if left unchanged:
- SSH allows password login: Bot scanners constantly sweep the entire internet. A newly public IP is typically probed within minutes — trying thousands of username/password combinations like root/123456, admin/admin, ubuntu/ubuntu.
- UFW is installed but not enabled: All ports are reachable from the outside. By default.
- Packages may be months out of date: The ISO was built at release time and may contain vulnerabilities that have long since been patched.
- No swap: Cloud VPS instances typically skip the swap partition. When RAM fills up, the OOM killer steps in without warning.
- Default timezone is UTC: Logs show
03:00when it’s actually 10 AM local time — a debugging nightmare.
Steps to Take — In Order of Priority
1. Update the System Immediately
Before anything else. Deploying before updating is an unnecessary risk:
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
If the kernel was updated, reboot immediately:
sudo reboot
2. Create a Dedicated User — Don’t Use Root
Logging in directly as root is bad practice — one wrong command can break the entire system with nothing to stop it. Create a new user with sudo privileges:
adduser deploy
usermod -aG sudo deploy
Log in with the new user to test permissions before locking out root.
3. Set Up SSH Keys and Disable Password Login
Brute-force bots try passwords — disabling password login eliminates that attack vector entirely. On your local machine:
# Create an SSH key if you don't have one
ssh-keygen -t ed25519 -C "[email protected]"
# Copy the public key to the server
ssh-copy-id deploy@your-server-ip
Once you’ve successfully logged in with the key, edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Edit these three lines:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart sshd
Important: Keep your existing SSH session open while testing the new one. If you get locked out, the old session is still there to fix things.
4. Enable UFW Firewall
Only open ports you actually need, close everything else:
# Allow SSH first — required, or you'll lock yourself out
sudo ufw allow ssh
# If running a web server
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable firewall
sudo ufw enable
# Check result
sudo ufw status verbose
Running SSH on a custom port, like 2222? Add this line before enabling:
sudo ufw allow 2222/tcp
5. Configure the Timezone
If your server is deployed for Vietnamese users, set the timezone accordingly — it makes reading logs much less confusing:
sudo timedatectl set-timezone Asia/Ho_Chi_Minh
# For a server in Japan, use this instead
sudo timedatectl set-timezone Asia/Tokyo
# Check
timedatectl status
6. Create a Swap File
That 2 AM OOM incident I mentioned at the top? The root cause was no swap. Any Node.js process that used a bit too much RAM got killed immediately — no buffer. For a cloud VPS with 1-2GB RAM, a 2GB swap file is a reasonable starting point:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Check
free -h
# Auto-mount on reboot
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Linux starts using swap fairly early by default (swappiness=60). Production servers should set this to 10 — only use swap when truly necessary:
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
7. Install Essential Packages
These tools aren’t always needed right away, but when you need to debug and they’re missing, it’s genuinely frustrating:
sudo apt install -y \
curl wget git vim htop \
net-tools dnsutils \
unzip build-essential \
fail2ban
8. Configure Fail2ban
Even with password login disabled, bots keep hammering the connection and spamming your logs. Fail2ban automatically blocks IPs after N failed attempts:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check status — how many IPs have been blocked
sudo fail2ban-client status sshd
9. Enable Automatic Security Updates
Nobody remembers to manually update the server every week. Let it handle itself:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Choose “Yes” when prompted. Only security patches are applied automatically — not all packages — so there’s no risk of an update breaking your application.
10. Check What Services Are Running
Final step: review whether anything unnecessary is exposed to the outside:
# Which ports are listening?
sudo ss -tlnp
# Which services are running?
sudo systemctl list-units --type=service --state=running
Disable anything you don’t need:
sudo systemctl disable --now snapd # Disable if you don't use snap
sudo systemctl disable --now bluetooth # Servers don't need Bluetooth
Bundle It All Into a Script for Next Time
After going through this 4-5 times, I wrote a script. One command covers the first 6 steps — no need to remember each one:
#!/bin/bash
# bootstrap-ubuntu.sh — Run immediately after installing Ubuntu Server 22.04
set -e
echo "[1/6] Updating system..."
apt update && apt upgrade -y && apt autoremove -y
echo "[2/6] Installing essential packages..."
apt install -y curl wget git vim htop net-tools dnsutils unzip fail2ban
echo "[3/6] Setting up swap..."
if [ ! -f /swapfile ]; then
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
echo 'vm.swappiness=10' >> /etc/sysctl.conf
fi
echo "[4/6] Configuring UFW..."
ufw allow ssh
ufw --force enable
echo "[5/6] Enabling Fail2ban..."
systemctl enable fail2ban
systemctl start fail2ban
echo "[6/6] Enabling auto security updates..."
apt install -y unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
echo "Done! Don't forget to:"
echo " - Create a non-root user"
echo " - Set up SSH key auth and disable password login"
echo " - Set correct timezone: timedatectl set-timezone Asia/Ho_Chi_Minh"
Run as root:
chmod +x bootstrap-ubuntu.sh
sudo ./bootstrap-ubuntu.sh
Quick Checklist Before Deploying
Once everything is done, run these quick commands to make sure nothing was missed:
# Is UFW enabled?
sudo ufw status
# Is SSH password login disabled?
grep -E 'PermitRootLogin|PasswordAuthentication' /etc/ssh/sshd_config
# Is swap active?
free -h
# Is the timezone correct?
date
# Is Fail2ban running?
sudo fail2ban-client status
The whole thing takes about 15-20 minutes. The first time might take longer as you read through each step’s output — the second time, just run the script and you’re done. Restoring from backup after an incident cost me nearly 4 hours, not counting the half day of debugging afterward to make sure nothing else was broken.

