Let's get to the point. The purpose of this article is to show you the steps I personally took to secure my brand new Droplet (VPS) from DigitalOcean.
I have separated this guide into three articles: initial setup, firewalls, and Zero Trust. This specific article covers the initial setup — the absolute "must have" settings for your server.
If you've already set up a sudo user, SSH keys, and disabled root login before, skip to section 6 — application-specific users. That's where this guide diverges from the standard playbook.
Note: I'm running Ubuntu 24.x LTS. If you are running a different OS, the tools are likely the same, but you may need to find the equivalent commands for your specific distribution.
1. Initial Setup & Updates
First, SSH into your server as root.
Run the following to ensure all your packages have the latest updates and security patches.
apt update && apt upgrade -y
2. Set Your Timezone (Optional)
It is good practice to synchronize your server time with your local time or the server's region.
# Set timezone to Asia/Jakarta (or replace with your own)
timedatectl set-timezone Asia/Jakarta
# Verify the change
timedatectl
3. Create a Sudo User
You should never operate as root for daily tasks.
Create a new user and give them sudo privileges. Replace myusername with your desired username. You'll be prompted to set a password and optional details. Set a strong password — we'll disable password auth later.
# Create the new user
adduser myusername
# Add the user to the sudo group
usermod -aG sudo myusername
Verify Sudo Access
Switch to your new user and test:
su - myusername
sudo whoami
The
-is important. Without it,sudoesn't load the new user's environment (PATH, HOME), and commands likesudocan behave unexpectedly.
4. SSH Key-Based Authentication
Using a password to access your server is vulnerable to brute force attacks. Let's set up SSH keys instead.
Run This on Your Local Machine
Fire up Terminal and generate an SSH key pair if you don't have one:
ssh-keygen -t ed25519 -C "[email protected]"
Press Enter to accept defaults. This creates:
- Private key:
~/.ssh/id_ed25519— keep this secret! - Public key:
~/.ssh/id_ed25519.pub— this goes on servers.
Copy Your Public Key to the Server
ssh-copy-id myusername@your_server_ip
Enter your password when prompted. This copies your public key to ~/.ssh/authorized_keys on the server.
Test Key-Based Login
ssh myusername@your_server_ip
You should log in without a password prompt.
Some VPS providers like DigitalOcean actually provide this during VPS creation — you can drop your public key in during the setup process and skip this step entirely.
5. Harden SSH Configuration
Now back on your server, let's lock down SSH by disabling root login and password authentication.
Edit SSH Configuration
sudo nano /etc/ssh/sshd_config
Find and modify these lines (uncomment if needed):
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
UsePAM yes
Why
UsePAM yes? You might see guides that setUsePAM no. On Ubuntu, PAM handles session setup,motd, and account management. Disabling it can breaksudoin edge cases and prevent password changes. Keep it enabled —PasswordAuthentication noandKbdInteractiveAuthentication noare what actually block password login.
Why
KbdInteractiveAuthenticationinstead ofChallengeResponseAuthentication? The old name was deprecated in OpenSSH 8.7. Ubuntu 24.04 ships OpenSSH 9.6. The old name still works as an alias but may be removed in future versions.
Watch Out for Drop-In Overrides
This is the part most guides miss. Ubuntu 24.04's sshd_config includes a line at the top:
Include /etc/ssh/sshd_config.d/*.conf
DigitalOcean's default image ships with /etc/ssh/sshd_config.d/50-cloud-init.conf, which often contains PasswordAuthentication yes. Because Include is processed first, this overrides your setting in the main config. Your password auth stays enabled even though you think you disabled it.
Check and fix it:
# See what's in the drop-in directory
ls /etc/ssh/sshd_config.d/
# Check if any file re-enables password auth
grep -r "PasswordAuthentication" /etc/ssh/sshd_config.d/
# If 50-cloud-init.conf exists with PasswordAuthentication yes, either:
# Option A: Remove the file (if you don't need cloud-init SSH config)
sudo rm /etc/ssh/sshd_config.d/50-cloud-init.conf
# Option B: Override it
echo "PasswordAuthentication no" | sudo tee /etc/ssh/sshd_config.d/60-disable-password.conf
Restart SSH Service
sudo systemctl restart ssh
Note: On Ubuntu, the SSH service is named
ssh, notsshd. Runningsystemctl restart sshdmay silently do nothing.
Test Before You Logout!
Warning: Open a new terminal and test SSH access before closing your current session:
ssh myusername@your_server_ipIf it works, great! If not, you still have your original session to fix things.
6. Create Application-Specific Users
This is a principle I follow religiously: one user per application. If an app gets compromised, the attacker is sandboxed to that user's permissions.
Example: Creating a User for a Web App
Let's say you're running a Node.js app called "portfolio":
sudo adduser --system --group --no-create-home portfolio
Breaking this down:
--system— Creates a system user (no login shell by default)--group— Creates a group with the same name--no-create-home— No home directory needed for service accounts
Example: Creating a User for a Database
sudo adduser --system --group --no-create-home dbuser
Why This Matters
If your portfolio app is compromised, the attacker runs as the portfolio user — not root, not your sudo user. They can't touch other apps or system files.
7. Sudoers Configuration
Not all sudo users need full root access. Let's configure granular permissions.
Edit Sudoers Safely
Never edit /etc/sudoers directly. Always use:
sudo visudo
This validates syntax before saving, preventing lockouts.
Verify Your User Has Sudo Access
In section 3, we added myusername to the sudo group. Ubuntu's default sudoers file already includes %sudo ALL=(ALL:ALL) ALL, which grants full sudo to all members of the sudo group. So your user already has full sudo access — no additional visudo entry needed.
You can verify this:
sudo -l -U myusername
If you want to add an explicit entry anyway (some admins prefer the visibility), add this line at the end:
myusername ALL=(ALL:ALL) ALL
But it's redundant with the group membership.
Optional: Allow Sudo Without Password
If you want to skip password prompts for sudo commands:
myusername ALL=(ALL:ALL) NOPASSWD:ALL
Warning: Only do this if you're the sole admin and understand the risks.
Grant Limited Sudo to Application Users (Optional)
If an app user needs a specific command — e.g., restarting a service:
portfolio ALL=(ALL) NOPASSWD: /bin/systemctl restart portfolio.service
8. Basic System Hardening
Install Essential Security Tools
sudo apt install -y ufw fail2ban unattended-upgrades
We'll configure these in Part 2, but installing them now ensures they're ready.
Enable Automatic Security Updates
sudo dpkg-reconfigure -plow unattended-upgrades
Select Yes to enable automatic updates for security patches.
Set Up a Basic Firewall
We'll go deep on firewalls in Part 2, but let's do a quick setup now:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw enable
Important: We're temporarily allowing SSH so you don't lock yourself out. In Part 3, we'll remove this and use Cloudflare Tunnel exclusively.
9. Security Verification
Let's verify everything is working.
Check SSH Configuration
sudo sshd -t
No output means the configuration is valid.
Also verify password auth is actually disabled (including drop-in overrides):
sudo sshd -T | grep -i passwordauthentication
This should print passwordauthentication no. If it says yes, a drop-in config file is overriding your setting — go back to section 5 and check /etc/ssh/sshd_config.d/.
Verify User Permissions
groups myusername
You should see myusername sudo.
Test Sudo Access
sudo apt update
Should work without issues.
Check Running Services
sudo systemctl status ssh
sudo systemctl status ufw
Both should be active and running.
Best Practices & Additional Tips
Use SSH Config for Easy Access
On your local machine, edit ~/.ssh/config:
Host myserver
HostName your_server_ip
User myusername
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
Now you can connect with just:
ssh myserver
Keep Your Local Keys Safe
Your private SSH key is the keys to the kingdom. Never share it, never commit it to git, and consider adding a passphrase:
ssh-keygen -p -f ~/.ssh/id_ed25519
What's Next?
Your server now has a solid security foundation:
- ✅ Dedicated sudo user with SSH key authentication
- ✅ Root login disabled
- ✅ Application-specific users for least privilege
- ✅ Properly configured sudoers
- ✅ Basic system hardening
But we're not done yet. Your server still has SSH exposed on port 22, and we haven't implemented a defense-in-depth firewall strategy.
In Part 2, we'll:
- Configure UFW to deny all ingress traffic
- Set up DigitalOcean's cloud firewall for an additional layer
- Install and configure Fail2Ban to block malicious IPs
- Verify that your server has zero open ingress ports
🔒