Ubuntu Server Initial Setup: 9 Steps to Start on a Fresh Ubuntu Server
When you get a new server, you run through an Ubuntu server initial setup once before putting any services on it. On an Ubuntu 24.04 server created at DigitalOcean, we connected as root and set things up in order, from package updates to a sudo user, key login, the firewall, the time zone, automatic security updates and swap, then confirmed that the settings stayed in place after a reboot.
The result boxes below are the output of running the commands in this order. The server IP 203.0.113.10 is an example value, and the new regular user is named deploy.
1️⃣ Ubuntu package update
Even a newly created server has updates waiting to be installed. After downloading a fresh package list, check what will change before actually installing anything.
🔲 apt-get update
export DEBIAN_FRONTEND=noninteractiveThe apt-get commands below were run with this value set first. With it, no configuration prompts appear during package installation and the default answers are applied.
apt-get updateHit:1 http://mirrors.digitalocean.com/ubuntu noble InRelease
Hit:2 http://mirrors.digitalocean.com/ubuntu noble-updates InRelease
Hit:3 http://mirrors.digitalocean.com/ubuntu noble-backports InRelease
Hit:4 https://repos-droplet.digitalocean.com/apt/droplet-agent main InRelease
Hit:5 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists...🔲 apt-get -s upgrade
apt-get -s upgradeCalculating upgrade...
The following upgrades have been deferred due to phasing:
libsqlite3-0
The following packages will be upgraded:
apt apt-transport-https apt-utils bind9-dnsutils bind9-host bind9-libs
cloud-init distro-info-data gir1.2-glib-2.0 git git-man libapt-pkg6.0t64
libbrotli1 libglib2.0-0t64 libglib2.0-bin libglib2.0-data liblzma5
libmspack0t64 libpcre2-8-0 python3-jwt python3-twisted tmux
ubuntu-pro-client ubuntu-pro-client-l10n xz-utils zlib1g
26 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.The -s option is a simulated run that only calculates the result without installing anything. On this server, 26 packages were to be upgraded, and libsqlite3-0 was left out of this upgrade as [deferred due to phasing]. The number of packages differs from server to server, depending on when the server was created and the image used.
🔲 apt-get -y upgrade
apt-get -y upgrade26 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
Need to get 15.4 MB of archives.
After this operation, 398 kB of additional disk space will be used.After the installation finishes, a list of restarted services follows.
Running kernel seems to be up-to-date.
Restarting services...
/etc/needrestart/restart.d/systemd-manager
systemctl restart cron.service fwupd.service multipathd.service packagekit.service polkit.service ssh.service systemd-journald.service systemd-networkd.service systemd-resolved.service systemd-timesyncd.service systemd-udevd.service udisks2.service
Service restarts being deferred:
systemctl restart ModemManager.service
/etc/needrestart/restart.d/dbus.service
systemctl restart systemd-logind.service
systemctl restart unattended-upgrades.serviceWhen an update that needs a reboot is installed, the /var/run/reboot-required file is created. This time [Running kernel seems to be up-to-date.] appeared and the file was not created, so we moved on to the next step without rebooting.
2️⃣ Ubuntu add user and sudo privileges
Do everyday work as a regular user and run only administrative commands with sudo. sudo raises privileges with your own password instead of the root password, so there is a record of who ran which command.
🔲 adduser deploy
adduser deployinfo: Adding user `deploy' ...
info: Selecting UID/GID from range 1000 to 59999 ...
info: Adding new group `deploy' (1000) ...
info: Adding new user `deploy' (1000) with group `deploy (1000)' ...
info: Creating home directory `/home/deploy' ...
info: Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for deploy
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
info: Adding new user `deploy' to supplemental / extra groups `users' ...
info: Adding user `deploy' to group `users' ...adduser wraps useradd; it creates the home folder and group, then asks for a password and user information in turn. The password does not appear on screen as you type, and details such as name and phone number can be left blank with Enter.
🔲 usermod -aG sudo deploy
usermod -aG sudo deploygroups deploydeploy : deploy sudo users🔲 su
Users in the sudo group can run administrative commands with sudo. Switch to the deploy account with su and check its privileges.
su - deployTo run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.whoamideploysudo whoami[sudo] password for deploy:
rootexitWith sudo, you are asked for deploy's password, and if it is correct the command runs with root privileges.
3️⃣ Register an SSH key for the new user
At first, an SSH key is registered only for root, so key login does not work for deploy. Copy root's authorized_keys into deploy's home folder.
🔲 install -o deploy -g deploy
install -d -m 700 -o deploy -g deploy /home/deploy/.sshinstall -m 600 -o deploy -g deploy /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keysls -la /home/deploy/.sshtotal 12
drwx------ 2 deploy deploy 4096 Nov 5 11:48 .
drwxr-x--- 3 deploy deploy 4096 Nov 5 11:48 ..
-rw------- 1 deploy deploy 100 Nov 5 11:48 authorized_keysinstall creates the folder, copies the file and sets the owner and permissions in one go. sshd checks the permissions and owner of the user's files before accepting a login (StrictModes), so set the .ssh folder to 700 and authorized_keys to 600.
🔲 ssh deploy@203.0.113.10
Do not close the root window that is open now; first check in a new terminal window that you can connect as deploy.
ssh deploy@203.0.113.10whoamideploy4️⃣ Block SSH root login
In Ubuntu 24.04, the Include line at the top of sshd_config loads the files in the /etc/ssh/sshd_config.d/ folder first, and for the same setting the value read first applies. So before changing anything, check the value that applies now and the file that contains it.
/etc/ssh└─ sshd_config at the top: Include /etc/ssh/sshd_config.d/*.conf ├─ ① sshd_config.d/50-cloud-init.conf PasswordAuthentication no ├─ ② sshd_config.d/60-cloudimg-settings.conf PasswordAuthentication no └─ ③ sshd_config line 42 PermitRootLogin yes → no
🔲 sshd -T
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication) 'permitrootlogin yes
passwordauthentication nosudo grep -rn 'PasswordAuthentication' /etc/ssh/sshd_config.d//etc/ssh/sshd_config.d/60-cloudimg-settings.conf:1:PasswordAuthentication no
/etc/ssh/sshd_config.d/50-cloud-init.conf:1:PasswordAuthentication noPassword login is already turned off in two files inside sshd_config.d, so the only change is the root login line in the main file.
🔲 sed -i PermitRootLogin
sudo grep -n '^PermitRootLogin' /etc/ssh/sshd_config42:PermitRootLogin yessudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_configsudo grep -n '^PermitRootLogin' /etc/ssh/sshd_config42:PermitRootLogin no🔲 systemctl reload ssh
sudo sshd -tsudo systemctl reload sshsudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication) 'permitrootlogin no
passwordauthentication nosshd -t checks the configuration syntax, and if nothing is printed there are no errors. After reloading, confirm in a new window that the deploy login still works, then try connecting as root and see that it is refused.
ssh root@203.0.113.10root@203.0.113.10: Permission denied (publickey).Even with the right key you cannot log in as root; administrative work is done by connecting as deploy and using sudo.
5️⃣ Ubuntu firewall ufw
ufw is Ubuntu's default firewall tool, but it is off at first. The moment it is turned on, any connection that is not allowed is blocked, so add the SSH allow rule first and turn it on last.
sudo ufw status verboseStatus: inactive🔲 ufw allow OpenSSH
sudo ufw app listAvailable applications:
OpenSSHsudo ufw allow OpenSSHRules updated
Rules updated (v6)sudo ufw allow 80/tcpRules updated
Rules updated (v6)sudo ufw allow 443/tcpRules updated
Rules updated (v6)sudo ufw show addedAdded user rules (see 'ufw status' for running firewall):
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcpOpenSSH is a profile that opens port 22, and 80 and 443 are the ports used by a web server. The rules are added for both IPv4 and IPv6.
🔲 ufw enable
sudo ufw enableCommand may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startupsudo ufw status verboseStatus: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
443/tcp (v6) ALLOW IN Anywhere (v6) Incoming connections are blocked by default, and with only the three allowed ports open, the firewall also starts at boot. After turning it on, connect again as deploy in a new window and check the rule numbers.
sudo ufw status numberedStatus: active
To Action From
-- ------ ----
[ 1] OpenSSH ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] OpenSSH (v6) ALLOW IN Anywhere (v6)
[ 5] 80/tcp (v6) ALLOW IN Anywhere (v6)
[ 6] 443/tcp (v6) ALLOW IN Anywhere (v6) 6️⃣ Ubuntu time zone setup
The initial time zone is Etc/UTC. To match log and scheduled job times to the server's region, this server, created in the Singapore region, was set to Asia/Singapore.
timedatectl Local time: Thu 2026-11-05 12:00:31 UTC
Universal time: Thu 2026-11-05 12:00:31 UTC
RTC time: Thu 2026-11-05 12:00:31
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no🔲 timedatectl list-timezones
timedatectl list-timezones | grep SingaporeAsia/Singapore🔲 timedatectl set-timezone
sudo timedatectl set-timezone Asia/Singaporetimedatectl Local time: Thu 2026-11-05 20:00:32 +08
Universal time: Thu 2026-11-05 12:00:32 UTC
RTC time: Thu 2026-11-05 12:00:32
Time zone: Asia/Singapore (+08, +0800)
System clock synchronized: yes
NTP service: active
RTC in local TZ: noOnly the Local time and Time zone lines change; clock synchronization (NTP) and UTC time stay the same.
7️⃣ unattended upgrades automatic security updates
Ubuntu installs security updates automatically with the unattended-upgrades package, which is installed and enabled from the start. So in this step, check that it is enabled and what it installs automatically.
🔲 dpkg-query -W
dpkg-query -W unattended-upgradesunattended-upgrades 2.9.1+nmu4ubuntu1systemctl is-enabled unattended-upgrades apt-daily.timer apt-daily-upgrade.timerenabled
enabled
enabled🔲 cat 20auto-upgrades
cat /etc/apt/apt.conf.d/20auto-upgradesAPT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";The 1 in both values means once a day, and changing them to 0 turns automatic updates off. What gets installed is decided by the 50unattended-upgrades file.
grep -vE '^[[:space:]]*(//|$)' /etc/apt/apt.conf.d/50unattended-upgradesUnattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
};
Unattended-Upgrade::DevRelease "auto";The allowed origins include -security but not -updates, so regular updates are not installed automatically and only security updates are. To receive regular updates automatically as well, uncomment the -updates line in this file.
🔲 unattended-upgrade
sudo unattended-upgrade --dry-run --debugStarting unattended upgrades script
Allowed origins are: o=Ubuntu,a=noble, o=Ubuntu,a=noble-security, o=UbuntuESMApps,a=noble-apps-security, o=UbuntuESM,a=noble-infra-securityThese are the last two lines of the same run.
No packages found that can be upgraded unattended and no pending auto-removals
The list of kept packages can't be calculated in dry-run mode.--dry-run calculates only what the automatic update would do, without installing anything. Since the package updates had already been done, it reported no packages to install automatically. Records of actual runs are kept in the /var/log/unattended-upgrades/ folder.
sudo ls -l /var/log/unattended-upgrades/total 32
-rw-r--r-- 1 root root 0 Nov 5 20:00 unattended-upgrades-dpkg.log
-rw-r--r-- 1 root root 0 Nov 5 17:34 unattended-upgrades-shutdown.log
-rw-r--r-- 1 root root 29093 Nov 5 20:04 unattended-upgrades.log8️⃣ Ubuntu swap memory setup
This server has 1GB of memory and no swap at first. Before putting any apps on it, create a 2GB swap file and register it in /etc/fstab so that it is turned on after a reboot as well.
free -h total used free shared buff/cache available
Mem: 961Mi 328Mi 161Mi 4.0Mi 631Mi 633Mi
Swap: 0B 0B 0Bdf -h /Filesystem Size Used Avail Use% Mounted on
/dev/vda1 24G 2.0G 22G 9% /🔲 fallocate -l 2G /swapfile
sudo fallocate -l 2G /swapfilesudo chmod 600 /swapfilels -lh /swapfile-rw------- 1 root root 2.0G Nov 5 20:04 /swapfile🔲 mkswap /swapfile
sudo mkswap /swapfileSetting up swapspace version 1, size = 2 GiB (2147479552 bytes)
no label, UUID=0b43ccd8-5b4a-4048-9b84-eee12667b387🔲 swapon /swapfile
sudo swapon /swapfileswapon --showNAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2free -h total used free shared buff/cache available
Mem: 961Mi 328Mi 160Mi 4.0Mi 632Mi 632Mi
Swap: 2.0Gi 0B 2.0Gi🔲 tee -a /etc/fstab
swapon only turns it on for now; to have it turned on after a reboot as well, add one line to fstab.
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab/swapfile none swap sw 0 09️⃣ Ubuntu server initial setup check
Once you confirm that the settings survive a reboot, the Ubuntu initial setup is complete.
🔲 systemctl reboot
sudo systemctl reboot🔲 uptime -p
After the reboot, connect again as deploy.
uptime -pup 2 minutesswapon --showNAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication) 'permitrootlogin no
passwordauthentication nosudo ufw status verboseStatus: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
443/tcp (v6) ALLOW IN Anywhere (v6) timedatectl | head -n 4 Local time: Thu 2026-11-05 20:06:35 +08
Universal time: Thu 2026-11-05 12:06:35 UTC
RTC time: Thu 2026-11-05 12:06:35
Time zone: Asia/Singapore (+08, +0800)systemctl is-active unattended-upgrades ssh.socketactive
activeSwap, SSH settings, the firewall, the time zone and automatic updates are all the same as before the reboot. Next, we will deploy a Node.js app on this server with PM2 and Nginx.
🔢 FAQ & Recommended Content
SSH settings
SSH settings - /etc/ssh/sshd_config file features and settings
Covers the sshd_config options that are often changed besides PermitRootLogin, and what they mean.
Swap
Swap Memory setup: 2 methods, partition & file
Covers how to create swap on a partition instead of a swap file, and the swapon and mkswap commands.
Packages
[Ubuntu] apt command | apt-get command: usage and differences
Covers the difference between apt and apt-get, and the commands for installing and removing packages.
ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.