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, su doesn't load the new user's environment (PATH, HOME), and commands like sudo can 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 set UsePAM no. On Ubuntu, PAM handles session setup, motd, and account management. Disabling it can break sudo in edge cases and prevent password changes. Keep it enabled — PasswordAuthentication no and KbdInteractiveAuthentication no are what actually block password login.

Why KbdInteractiveAuthentication instead of ChallengeResponseAuthentication? 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, not sshd. Running systemctl restart sshd may 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_ip

If 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

🔒