Home · About · Services · Industries · Projects · Products · Process · Blog · FAQ · Contact
Ubuntu server initial setup: the first hour, step by step What we do in the first hour on every new Ubuntu 24.04 or 26.04 server: updates, a sudo user, SSH lockdown, UFW, automatic updates, swap and log limits.
A new VPS has just come up. You have a public IP, a root password or a key, and a list of things to deploy. Within minutes, bots will be trying passwords against port 22. What you do in the next hour decides whether this machine is easy to run for years or a slow-burning problem.
Short answer: in the first hour on a new Ubuntu server, update packages, create a named sudo user with SSH key login, disable root and password logins over SSH, turn on the UFW firewall allowing only SSH and the ports you serve, confirm automatic security updates, set the time zone and hostname, add swap, and cap log size.
This is the routine we follow on Ubuntu 24.04 LTS and 26.04 LTS servers, whether they sit on AWS, Azure or a plain VPS. Every command below works on both. Run them in order, and keep a second SSH session open whenever you touch SSH or the firewall.
Update first, then reboot if the kernel changed The image your provider launched may be weeks old. Patch before you install anything else, so your own software lands on a current base.
sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove --purge -y
# Reboot only if an update asks for it
if [ -f /var/run/reboot-required ]; then
cat /var/run/reboot-required.pkgs 2>/dev/null
sudo reboot
fiWhile it reboots, check the provider console. Note whether the machine has a provider-level firewall or security group in front of it. On AWS and Azure it almost always does, and it is your first layer. The host firewall we set up later is the second.
A named user with SSH keys Some providers give you root, others a default user such as ubuntu. Either way, create a personal account for each person who will log in. Shared accounts make it impossible to tell who did what, and impossible to revoke one person cleanly.
sudo adduser asha
sudo usermod -aG sudo asha
# Copy the key you already log in with
sudo install -d -m 700 -o asha -g asha /home/asha/.ssh
sudo cp ~/.ssh/authorized_keys /home/asha/.ssh/authorized_keys
sudo chown asha:asha /home/asha/.ssh/authorized_keys
sudo chmod 600 /home/asha/.ssh/authorized_keysBetter still, each person adds their own public key. Use Ed25519 keys (ssh-keygen -t ed25519) and protect the private key with a passphrase. Now open a new terminal and log in as the new user. Run sudo -v to confirm sudo works. Do not move on until both checks pass.
Lock down SSH, carefully Ubuntu reads extra SSH settings from files in /etc/ssh/sshd_config.d/. There is a trap here. For most options, sshd uses the first value it finds, and the files are read in alphabetical order. Many cloud images ship 50-cloud-init.conf, which may set PasswordAuthentication yes. A file named 99-hardening.conf would lose to it. So name yours with a low number.
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf >/dev/null <<'CONF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
AllowUsers asha
CONF
# Check syntax, then check the values sshd will actually use
sudo sshd -t
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|kbdinteractive|allowusers'
sudo systemctl restart sshThe sshd -T output is the real test. It prints the effective configuration after all files are merged, so it catches the cloud-init override. AllowUsers is a whitelist: anyone not listed, including the default ubuntu user, can no longer log in, so add every admin account before you restart. Keep your existing session open, then open a fresh one to prove you can still get in. Our SSH hardening checklist goes further, into key rotation and access reviews.
A note on ports. Since Ubuntu 22.10, SSH is socket-activated: systemd listens on the port and starts sshd on demand. If you change Port in a config file, run sudo systemctl daemon-reload and then sudo systemctl restart ssh.socket, or the change will not take effect. We usually leave SSH on 22. Moving it cuts log noise but adds no real security once passwords are off. Restricting port 22 to known IPs, or reaching the server only over a Tailscale network, does much more.
Firewall: deny by default UFW is installed but inactive on Ubuntu Server. Allow SSH before you enable it, or you will lock yourself out.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseOnly open web ports if the server serves web traffic. Databases, Redis and admin panels should never be open to the internet. Bind them to 127.0.0.1 or a private interface instead.
One warning if you will run Docker. Ports published with -p or a compose ports: entry are written straight into iptables by Docker and bypass UFW rules. A database published as 5432:5432 is public even when ufw status says otherwise. Publish as 127.0.0.1:5432:5432, or keep such services on an internal Docker network with no published port at all. Our Docker Compose production checklist covers this in more detail.
If you want to slow down brute-force attempts further, install fail2ban (sudo apt install fail2ban). Ubuntu's package enables an SSH jail by default. With key-only logins it mostly keeps logs quiet, but it is cheap. Our UFW and fail2ban guide covers rate limits, custom jails and checking bans.
Automatic security updates and reboots Ubuntu Server ships with unattended-upgrades, which installs security updates daily. Confirm it is switched on rather than assuming.
cat /etc/apt/apt.conf.d/20auto-upgrades
# Expect:
# APT::Periodic::Update-Package-Lists "1";
# APT::Periodic::Unattended-Upgrade "1";
# If missing, enable it:
sudo dpkg-reconfigure -plow unattended-upgrades
# Dry run to see what it would do
sudo unattended-upgrade --dry-run --debug | tail -n 20Updated kernels and some libraries do not take effect until a reboot. Decide who reboots, and when. For a single server running stateless containers, an automatic reboot in a quiet window is usually the right call. Add it in a drop-in file so package updates do not overwrite it. The reboot time follows the server's local time zone.
sudo tee /etc/apt/apt.conf.d/52reboot >/dev/null <<'CONF'
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
CONFFor a database server, or anything where a surprise restart matters, leave automatic reboots off and schedule them by hand. Ubuntu Pro, free for a small number of personal machines, adds Livepatch, which applies many kernel security fixes without a reboot.
Time, hostname, swap and logs These take five minutes and save hours later.
Time zone: set it with sudo timedatectl set-timezone Asia/Kolkata (or keep UTC for servers in several regions). Check that timedatectl reports the clock as synchronised. TLS, TOTP and log correlation all break when the clock drifts. Hostname: give the server a meaningful name with sudo hostnamectl set-hostname api-prod-1, and add it to /etc/hosts so sudo does not complain. On cloud images, if the name reverts after a reboot, set preserve_hostname: true in a file under /etc/cloud/cloud.cfg.d/. Swap: many cloud images have none. A small swap file stops the out-of-memory killer from taking down your database during a brief spike. Logs: cap the systemd journal so it cannot fill the disk. # 2 GB swap file
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf
sudo sysctl --system
# Cap journal size
sudo mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=500M\n' | sudo tee /etc/systemd/journald.conf.d/size.conf
sudo systemctl restart systemd-journaldSwap is a safety net, not extra memory. If the server swaps constantly, it needs more RAM, not a bigger swap file.
Write it down, then automate it Before you close the terminal, record what you did. A short note in the project repository is enough: who has access, which ports are open and why, the reboot policy, and where backups go. The next person, including you in six months, will need it.
Once you have done this twice, put it in code. A cloud-init user-data file can create users, install keys and packages, and write the config files above on first boot. For more than a handful of servers, an Ansible playbook keeps them consistent over time. Either way, a new server comes up identical to the last one, and nobody forgets the SSH drop-in.
Then add the pieces that need more thought than an hour allows: monitoring and alerts for disk, memory and certificate expiry, and backups you have actually restored. Our post on backups that actually restore covers the second.
First-hour checklist Packages fully upgraded and the server rebooted if required. A named sudo user per person, logging in with an Ed25519 key. Root login and password authentication off, confirmed with sshd -T. UFW enabled with only SSH and the ports you serve; databases bound to localhost. Docker-published ports checked, since they bypass UFW. unattended-upgrades enabled, with a reboot policy someone has agreed. Time zone, hostname, swap and journal size set. Access, open ports and reboot policy written down. If the server will hold patient records, student data or payment details, or you are inheriting a machine nobody has documented, that is when a second pair of eyes is worth it. An audit of an existing server is often more revealing than setting up a new one.
Infigini IT Services · 11 Kanwali Road, Dehradun 248001, India · info@infigini.com · +91 95684 87789