Post

Linux VPS Setup and Hardening Guide

Learn to securely set up and harden a Virtual Private Server (VPS)

Linux VPS Setup and Hardening Guide

Buy a VPS privately

If you value privacy, choose providers that minimize data collection and support privacy-preserving payments:

Prefer paying with Bitcoin after a Whirlpool coinjoin, Bitcoin via Lightning, or Monero. These methods reduce linkage between your identity and your server. When possible, avoid providing personally identifying information during signup.

Choosing a Linux distro for your VPS (quick guide)

  • Ubuntu (Debian-based)
    • Pros: beginner-friendly, broad package support, many tutorials, frequent updates
    • Cons: more default services running, therefore more bloated
  • Debian (independent)
    • Pros: very stable, lightweight baseline, ideal for long-running or low-RAM servers
    • Cons: older packages by default, more manual setup early on
  • Alpine (independent)
    • Pros: extremely lightweight, fast boot, good security posture
    • Cons: not beginner-friendly, more manual configuration, no systemd, smaller community
  • Fedora (Red Hat family)
    • Pros: cutting-edge stack, SELinux by default, strong dev tooling
    • Cons: faster-moving base can require more maintenance and frequent updates

Rule of thumb: Ubuntu LTS or Debian Stable are solid defaults. If RAM is tight, lighter distros help.

Setup and Hardening guide

1) Inspect local SSH keys

1
ls -la ~/.ssh/

Lists your local SSH key files and permissions. You’ll use public keys to log in to the VPS; keep private keys protected with a strong passphrase.

1
rm ~/.ssh/<name>

Removes an old or unused key file. Do this only if you’re sure you won’t need it. Rotate keys rather than leaving stale credentials around.

1
cat ~/.ssh/*.pub

Displays all public keys. These are safe to share with your VPS provider or add to the server’s authorized_keys file.

1
cat ~/.ssh/<name>.pub

Shows a specific public key. Use the key that matches the private key you plan to use for login.

2) Generate SSH key for your VPS provider (local)

1
ssh-keygen

Generates a new SSH keypair interactively. Choose ed25519 when prompted and set a long passphrase. Store the passphrase in a password manager.

1
~/.ssh/<SSH_KEY_NAME>

This is the typical location for SSH keys. Replace <SSH_KEY_NAME> with a clear name (e.g., id_ed25519_vps) to keep your keys organized.

1
ls -la ~/.ssh/

Verifies that your new keys exist and have appropriate permissions.

1
cat ~/.ssh/<SSH_KEY_NAME>.pub

Displays your public key. Add this to your VPS provider before creating the server so you can log in without passwords.

3) First login and initial updates

1
ssh root@<VPS_IP>

Logs in to the new server as root using your provider-injected key. Use this only for the initial setup; you’ll create a non-root admin shortly.

1
ssh -i ~/.ssh/<SSH_KEY_NAME> root@<VPS_IP>

Explicitly selects your private key file if your agent has multiple keys loaded or the default isn’t correct.

1
apt update

Refreshes the package index so your system knows about the latest available updates.

1
apt upgrade -y

Upgrades installed packages to their newest versions. Security patches often arrive via these updates.

1
apt install unattended-upgrades

Installs the tool that automatically applies security updates.

1
dpkg-reconfigure -plow unattended-upgrades

Enables unattended security updates through a guided configuration. Choose to apply security fixes automatically.

1
nano /etc/apt/apt.conf.d/50unattended-upgrades

Open the config and ensure “-security” origin is allowed. Consider enabling automatic reboots during a maintenance window (e.g., 02:00) to apply kernel fixes safely.

1
unattended-upgrades --dry-run --debug

Performs a dry run to confirm that automatic updates are configured as intended, without making changes.

4) Create a non-root admin and verify sudo

1
adduser <USERNAME>

Creates your daily admin account. Use a strong unique password even if you primarily log in via SSH keys.

1
usermod -aG sudo <USERNAME>

Grants sudo privileges to your admin account for controlled elevation.

1
su - <USERNAME>

Switches to your new account so you can test its environment and permissions.

1
sudo whoami

Confirms sudo works (should print “root”). After this, avoid direct root logins and use your admin account with sudo.

5) Configure SSH key authentication and harden SSH

1
ssh-keygen -t ed25519 -C "<KEY_LABEL>"

Generates a modern local key (if you haven’t already). The label helps you track purpose or ownership of the key.

1
mkdir -p ~/.ssh

Ensures the SSH directory exists on the server for your admin user.

1
chmod 700 ~/.ssh

Sets strict permissions so OpenSSH will accept the directory.

1
touch ~/.ssh/authorized_keys

Creates the authorized_keys file for your admin user.

1
chmod 600 ~/.ssh/authorized_keys

Sets strict permissions on the authorized_keys file to avoid login failures.

1
cat ~/.ssh/id_ed25519.pub

Shows your local public key. You’ll paste this into the server’s authorized_keys.

1
echo "paste-your-public-key-here" >> ~/.ssh/authorized_keys

Appends your public key to the server’s authorized_keys. Only paste the “.pub” line, not the private key.

1
nano ~/.ssh/authorized_keys

Verify the key was added correctly and is on a single line. Remove duplicates or stale keys.

1
nano ~/.ssh/config

Optional: create a local SSH profile. Example:

Host vps-server
    HostName     User     IdentityFile ~/.ssh/id_ed25519

1
sudo nano /etc/ssh/sshd_config

Open the SSH daemon config to harden access. Disallow root logins and disable password authentication to force key-based access.

1
PermitRootLogin no

Prevents logins as root over SSH. Always log in as your admin user and use sudo for privilege escalation.

1
PasswordAuthentication no

Disables password-based SSH logins. This removes a major brute-force attack vector and ensures keys are required.

1
UsePAM no

Disables PAM modules for SSH. If you later need PAM features (e.g., 2FA), re-enable thoughtfully.

1
ChallengeResponseAuthentication no

Disables legacy interactive auth mechanisms, further tightening the SSH surface.

1
sudo cat /etc/ssh/sshd_config.d/50-cloud-init.conf

Checks for cloud-init overrides that might conflict with your hardening.

1
sudo cat /etc/ssh/sshd_config.d/60-cloudimg-settings.conf

Reviews additional layered settings; align them with your sshd_config.

1
sudo nano /etc/ssh/sshd_config.d/50-cloud-init.conf

Edit cloud-init drop-in config if needed to ensure passwords and root logins remain disabled.

1
sudo nano /etc/ssh/sshd_config.d/60-cloudimg-settings.conf

Update any defaults that re-enable weak SSH settings. Keep your original session open to avoid lockouts.

1
sudo systemctl restart ssh

Applies the SSH changes. Test a new SSH session in another terminal before closing the original one, so you can revert safely if needed.

6) Configure firewall (UFW)

1
sudo apt install ufw

Installs Ubuntu’s uncomplicated firewall. It’s a simple interface for default- deny inbound policies and explicit allowed ports.

1
sudo ufw default deny incoming

Blocks unsolicited inbound traffic. You will explicitly allow only what you need.

1
sudo ufw default allow outgoing

Allows outbound traffic by default so your server can reach package mirrors and APIs.

1
sudo ufw allow ssh

Opens the SSH port so you don’t lock yourself out. If you later change your SSH port, adjust this rule accordingly.

1
sudo ufw allow 80/tcp

Opens HTTP. Add only if you run a web server or reverse proxy.

1
sudo ufw allow 443/tcp

Opens HTTPS. Add only if you need encrypted web access.

1
sudo ufw enable

Activates the firewall with the current rules. Confirm access from a second terminal before closing your session.

1
sudo ufw status verbose

Displays active rules and policies so you can confirm the firewall is in the intended state.

7) Install and configure Fail2ban

1
sudo apt install fail2ban

Installs Fail2ban, which monitors logs and temporarily bans abusive IPs.

1
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Creates a local override file you can safely edit without changing defaults.

1
sudo nano /etc/fail2ban/jail.local

Enable the sshd jail and configure thresholds (e.g., maxretry=3, bantime=3600, findtime=600). Add ignoreip for trusted admin IPs to avoid accidental bans.

1
sudo systemctl enable fail2ban

Ensures Fail2ban starts automatically on boot.

1
sudo systemctl start fail2ban

Starts the service immediately so protections take effect.

1
sudo fail2ban-client status

Shows Fail2ban’s global status and enabled jails.

1
sudo fail2ban-client status sshd

Displays details for the sshd jail, including currently banned IPs.

8) Memory checks and swap file

1
sudo journalctl | grep -i "out of memory"

Searches logs for OOM events. Frequent OOMs indicate you should add RAM, reduce workloads, or add swap to buffer spikes.

1
sudo fallocate -l 2G /swapfile

Allocates a 2G swapfile. Adjust size based on your instance; small VPS hosts benefit from modest swap.

1
sudo chmod 600 /swapfile

Locks down permissions so the swapfile cannot be read by other users.

1
sudo mkswap /swapfile

Initializes the swapfile for use by the kernel.

1
sudo swapon /swapfile

Activates swap immediately.

1
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Makes swap persistent across reboots by adding it to fstab.

1
free -h

Verifies available memory and swap in a human-readable format.

9) Verification checklist

1
sudo sshd -T

Prints the effective SSH configuration as interpreted by the daemon, so you can confirm hardening settings are active.

1
sudo netstat -tulpn

Lists listening ports and owning processes. Requires net-tools. Use this to confirm only intended services are exposed.

1
sudo ufw status verbose

Displays firewall policies and open ports. Cross-check with netstat to ensure consistency.

1
sudo fail2ban-client status

Confirms Fail2ban is running and shows enabled jails.

1
sudo journalctl -fu sshd

Tails SSH logs in real time. Test a login and watch entries to verify behavior and investigate unexpected errors.

10) Regular maintenance tasks

1
sudo apt update

Refreshes package indexes so you see the latest available updates.

1
sudo apt upgrade

Applies updates. Schedule this weekly at minimum; more frequent for exposed services.

1
sudo tail -f /var/log/auth.log

Watches authentication logs in real time. Detects login attempts and unusual activity quickly.

1
sudo tail -f /var/log/fail2ban.log

Monitors Fail2ban’s actions and alerts so you know when IPs are being banned or unbanned.

1
sudo tail -f /var/log/ufw.log

Streams firewall logs to surface unexpected inbound hits or misconfigurations.

Security best practices

  1. Store SSH private keys securely with strong passphrases; back them up safely.
  2. Keep an inventory of authorized users and keys; remove stale accounts.
  3. Default-deny inbound with UFW; only open required ports.
  4. Rotate SSH keys periodically, especially when team membership changes.
  5. Monitor system resources, disk usage, and logs; set alerts for anomalies.
  6. Apply security updates promptly; unattended security upgrades help.
  7. Document custom ports and changes for easy auditing and troubleshooting.
  8. Prefer privacy-preserving VPS providers (e.g., 1984.hosting, mynymbox.net).
  9. Pay with Bitcoin after a Whirlpool coinjoin, Bitcoin via Lightning, or Monero to reduce identity linkage to your server.

Useful command list for beginners

System and OS information:

1
uname -a

Shows kernel and basic system info; useful for confirming environment.

1
hostnamectl

Displays and can set the system’s hostname and OS metadata.

1
uptime

Tells how long the server has been running and current load averages.

1
lsb_release -a

Prints distro details (if lsb-release is installed) for quick identification.

1
cat /etc/os-release

Shows OS name and version from the standard release file.

1
lscpu

Describes CPU model, cores, architecture—handy for sizing workloads.

Hardware and usage:

1
lspci

Lists PCI devices. In VPS, often minimal; more relevant on bare metal.

1
lsusb

Lists USB devices. In VPS, commonly empty; useful on physical hosts.

1
free -h

Shows memory usage with human-readable units.

1
df -h

Displays disk usage by filesystem, highlighting space pressure.

1
du -sh <directory>

Summarizes the size of a directory. Helps find large folders quickly.

Users and groups:

1
whoami

Prints your current username; confirms which account you’re operating under.

1
id

Shows user and group IDs as well as group memberships.

1
adduser <user>

Creates a user interactively with home directory and default settings.

1
usermod -aG sudo <user>

Adds a user to the sudo group for privilege escalation.

1
passwd <user>

Changes a user’s password. Use for initial setup or urgent rotation.

1
deluser <user>

Removes a user. Clean up accounts that no longer need access.

1
groups

Lists groups the current user is a member of.

1
groupadd <group>

Creates a new group, useful for organizing privileges.

1
gpasswd -a <user> <group>

Adds a user to a group via gpasswd (group password management).

1
last

Shows login history, useful for detecting unusual access patterns.

1
w

Shows who is logged in and their current activity.

Files and directories:

1
ls -l

Lists files with permissions, ownership, and timestamps.

1
ls -a

Includes hidden files (dotfiles) in listings.

1
cd <directory>

Changes the current directory.

1
pwd

Prints the present working directory.

1
mkdir <directoryname>

Creates a new directory.

1
rmdir <directoryname>

Removes an empty directory; fails if it contains files.

1
rm -rf <directory>

Recursively deletes a directory and all contents. Use with caution.

1
cp <file1> <file2>

Copies a file. Use -r for directories.

1
mv <file1> <file2>

Moves or renames a file or directory.

1
touch <file.txt>

Creates an empty file or updates its timestamp.

1
nano <file.txt>

Opens a file in the nano editor for quick edits.

1
cat <file.txt>

Prints file contents to the terminal.

1
less <file.txt>

Views a file with paging; search with “/pattern”, quit with “q”.

1
head -n 20 <file.txt>

Shows the first 20 lines of a file; adjust the number as needed.

1
tail -f /var/log/syslog

Streams a log in real time; great for debugging and monitoring.

Search and size:

1
find / -name "<name>.<file-type>"

Searches by name and extension, starting at root. May be slow; narrow paths for performance.

Networking:

1
ip a

Shows network interfaces and IP addresses; confirms connectivity config.

1
ifconfig

Legacy interface listing (requires net-tools). Use “ip” in modern setups.

1
ping <website>

Checks basic connectivity and round-trip latency.

1
traceroute <website>

Reveals the path packets take; useful for routing issues.

1
nslookup <website>

Queries DNS for a domain; confirm resolution and records.

1
dig <website>

Detailed DNS query tool; shows record TTLs and authoritative servers.

1
netstat -tulnp

Lists listening ports and processes (requires net-tools). Alternative: “ss”.

1
ss -tulnp

Modern tool to list sockets/ports without needing net-tools.

1
curl <URL>

Fetches URL content; great for testing endpoints and APIs.

1
wget <URL>

Downloads files over HTTP/HTTPS/FTP.

1
scp file user@server:/directory

Copies files over SSH; secure alternative to FTP.

1
rsync -av file user@server:/directory

Synchronizes files/directories efficiently; preserves attributes with “-a”.

1
ftp <server>

Connects to FTP servers (not recommended for secure workflows).

1
telnet host <port>

Tests TCP connectivity to a specific port; useful for quick checks.

Packages (Debian/Ubuntu):

1
apt update

Refreshes package lists; run before installing/upgrading.

1
apt upgrade

Upgrades packages; consider “-y” for automation with care.

1
apt full-upgrade

Upgrades including dependencies that may remove/install packages.

1
apt install <package>

Installs a package from the repositories.

1
apt remove <package>

Uninstalls a package but leaves config files.

1
apt purge <package>

Removes a package and its config files for a clean slate.

1
apt autoremove

Cleans up unused dependencies after removals.

1
apt dpkg -i <file.deb>

Installs a local .deb file; use with caution and verify signatures.

1
dpkg -l

Lists installed packages; useful for audits and troubleshooting.

Permissions:

1
chmod 755 <file>

Sets file permissions (rwx for owner, rx for group/others). Choose carefully.

1
chown <user>:<group> <file>

Changes file ownership to the specified user and group.

1
chgrp <group> <file>

Changes a file’s group ownership.

1
umask 022

Sets default permission mask for new files. Adjust to tighten defaults.

1
stat <file>

Shows detailed metadata and permissions for a file.

Extras:

1
alias ll='ls -la'

Sets a handy alias for detailed listings.

1
history

Displays your shell command history.

1
!<number>

Re-runs a specific command from history by its number.

1
clear

Clears the terminal screen for readability.

1
echo "Hello"

Prints a string to the terminal; useful for quick tests.

1
date

Shows the current date and time.

1
cal

Displays a calendar.

1
shutdown -h now

Shuts down the system immediately and halts power.

1
reboot

Restarts the system.

1
timedatectl

Shows or sets time and timezone; ensure correct time for TLS and logs.

1
crontab -e

Edits scheduled cron jobs for the current user.

Closing thoughts

This baseline focuses on strong, simple controls: SSH keys only, no root logins, default-deny firewall, abuse detection with Fail2ban, and routine updates.

This should help you get up and running with your first VPS securely.

Always remember to do your own research!

This post is licensed under CC BY 4.0 by the author.