Ubuntu VPS Setup Guide
Setting up your Ubuntu server · VPS Management

Step-by-step Ubuntu server setup and hardening guide.
Getting a VPS
You need a Linux VPS (Virtual Private Server) to follow this guide. Any Ubuntu LTS version works.
Some great VPS provider options are DigitalOcean, Hetzner, Linode, Vultr.
-
Hetzner Cloud - Best value for money. Excellent hardware specs, includes generous traffic allowances. Limited to fewer regions than other VPS providers. Their server auction offers dedicated hardware at cheap, transparent pricing.
-
DigitalOcean - Solid choice with good documentation and 14+ global regions. Pricing is middle-tier but you get reliable performance and excellent tutorials.
-
Linode - Consistent performance and responsive support. Good balance of features and pricing. Strong reputation in the developer community.
-
Vultr - Good global coverage with competitive pricing. Interface is straightforward, performance is reliable.
-
AWS Lightsail - If you’re already in the AWS ecosystem. More expensive but integrates well with other AWS services.
-
For most other “cheap VPS”, I recommend caution. They often have hidden costs, poor support, or unreliable performance.
Accessing Your VPS
After creating a VPS instance (for example your DigitalOcean droplet), you will be provided an IP address as well as login password. Use the credentials to log into the server.
ssh root@SERVER_IP_ADDRESS
Managing Users
By default, only one user exists: root - a superuser
Creating a new user
Create a new user to avoid using root and it’s elevated privileges. While creating a new user, several questions will be asked. You can add additional information about the user but note that only the password is essential.( Press ENTER to skip the rest)
adduser demouser
# you can now give the user access to root privileges (sudo)
gpasswd -a demouser sudo
Confirm that you can login as this new user before proceeding.
ssh newuser@SERVER_IP
sudo su - # try to access root as user
SSH Lockdown
Disabling root login
Before disabling root login, ensure that you’ve created a user that can SSH to the server (as explained in User Management section above).
The user can use the su
command to become root (will need root password), or if the user has sudo they can just sudo su -
to access root.
Edit /etc/ssh/sshd_config
and turn PermitRootLogin to no. Use a different editor if new to vi (ZZ to exit).
Warning: Before doing this, ensure that you can login with another (non-root) user account (see Managing Users above).
sudo vi /etc/ssh/sshd_config
# Change the following line
PermitRootLogin yes
# to
PermitRootLogin no
Restart ssh service for this change to effect.
sudo systemctl restart sshd
You can also setup public key authentication (recommended). This will enable you to securely login to the server without having to enter your password.
Public Key Authentication
Public Key authentication enables you to login to a server using ssh public key. This is my recommended method of authentication.
SSH Keys come in a pair; a private and public key.
Generate Key Pair
OpenSSH client is required, this is most likely already installed. You can install it with sudo apt-get -y install openssh-client
Generate Ed25519 Keys (Recommended)
Ed25519 provides strong security with smaller keys and fast performance.
ssh-keygen -t ed25519 -C "your_email@example.com"
For systems that don’t support Ed25519, use RSA with 4096 bits:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
The public and private keys will be generated in ~/.ssh/ as id_ed25519.pub and id_ed25519 respectively (or id_rsa.pub and id_rsa for RSA). The public key can safely be shared, however, NEVER share your private key.
In order to login with the generate ssh keys, append the contents of the public key to the following file in the VPS ~/.ssh/authorized_keys.
From your computer, you can run either of the following commands. They will require inputting your password once.
# ssh-copy-id is an command that copies the public key automatically
ssh-copy-id USERNAME@SERVER_IP
# manually copy the public file (one-line)
ssh USERNAME@SERVER_IP "echo \"$(cat ~/.ssh/id_rsa.pub)\" >> .ssh/authorized_keys"
You can now simply login without entering a password with ssh USERNAME@SERVER_IP
.
Disable Password Authentication (Optional): Edit /etc/ssh/sshd_config
sudo vi /etc/ssh/sshd_config
# Security settings
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
# Modern crypto only (comment out for older systems)
HostKey /etc/ssh/ssh_host_ed25519_key
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
Restart ssh service
sudo systemctl reload sshd
I would also recommend using a non-common port for SSH.
Using a custom SSH Port
Edit /etc/ssh/sshd_config
and change the Port number to a non-common SSH port (just pick an unused number more that 1025, note: don’t use 2222, it’s also common)
sudo vi /etc/ssh/sshd_config
# Uncomment and change Port number from
# Port 22
# to
Port 2222
# Restart ssh daemon service
sudo systemctl restart sshd
Update Server
- Update server software:
sudo apt-get update && sudo apt-get upgrade -y
- Set desired server hostname: As superuser, edit the files
/etc/hostname
and/etc/hosts
. To update without a restart restart, run this:systemctl restart systemd-logind.service
- Update the timezone, to your desired timezone. Use
dpkg-reconfigure tzdata
Docker Setup
Docker Installation
Follow the official installation guide: Install Docker Engine on Ubuntu
Add your user to docker group to avoid using sudo:
sudo usermod -aG docker $USER
# Log out and back in for group changes to take effect
Configure Docker Security
Edit /etc/docker/daemon.json
:
{
"icc": false,
"userland-proxy": false,
"no-new-privileges": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Restart Docker to apply configuration:
sudo systemctl restart docker
Run Docker without root privileges using rootless mode for additional security isolation.
Installation
Install rootless Docker:
# Install rootless Docker
curl -fsSL https://get.docker.com/rootless | sh
# Add to PATH
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc
source ~/.bashrc
# Enable systemd service for current user
systemctl --user enable docker
systemctl --user start docker
Benefits:
- Docker daemon runs as regular user
- No need for sudo privileges
- Additional isolation from host system
- Reduced attack surface
Limitations:
- Cannot bind to privileged ports (< 1024)
- Some networking features limited
- Performance overhead for certain workloads
Web Server
Caddy (Recommended)
Caddy provides automatic HTTPS without complex configuration.
Install Caddy: Installation Guide
Basic configuration in /etc/caddy/Caddyfile
:
example.com {
root * /var/www/example.com
file_server
encode gzip
}
# Reverse proxy with automatic HTTPS
api.example.com {
reverse_proxy localhost:8080
}
Start and enable Caddy:
sudo systemctl enable --now caddy
Note: Caddy obtains and renews SSL certificates automatically. No certbot needed.
Use nginx with Let's Encrypt certificates if you prefer traditional web server setup.
Installation
Install nginx:
sudo apt install nginx
Basic site configuration in /etc/nginx/sites-available/example.com
:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html index.php;
# SSL configuration (use certbot)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
location / {
try_files $uri $uri/ =404;
}
# Reverse proxy example
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enable site and restart nginx:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Install certbot for SSL:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Add Swap
Check if swap is already enabled on the server, if enabled, the swap partition will be listed.
sudo swapon -s
# alternatively
free -g
# shows free (and used) physical & swap memory (-g = in gb)
If swap is not enabled, check the amount disk space you have available.
df -h
Create swapfile (usu 2x or 1x your RAM)
# Fast Method (fallocate e.g creating 1gb swap)
sudo fallocate -l 1G /swapfile
# Slow method (e.g creating 4gb swap)
### sudo dd if=/dev/zero of=/swapfile bs=1G count=4
Verify space has been allocated
ls -lh /swapfile
Enable Swap
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
#verify it's enabled
sudo swapon -s
Enable Swap on Reboot/ Start: Edit fstab (/etc/fstab
), and add the swap filesystem.
sudo vi /etc/fstab
# Add this line to /etc/fstab
/swapfile none swap sw 0 0
If you are working on a RAM intensive app, e.g Java, consider reducing swappiness and inode cache rate.
Tweak Swappiness
Check the current swappiness (0-100)
cat /proc/sys/vm/swappiness
# default is usually 60
Change swappiness to your desired value.
sudo sysctl vm.swappiness=20
Enable on restarts
sudo vi /etc/sysctl.conf
# Add this line to /etc/sysctl.conf
vm.swappiness=10
Inode Cache Rate
# Check the current inode cache rate
cat /proc/sys/vm/vfs_cache_pressure #usu 60
# Change to desired value
sudo sysctl vm.vfs_cache_pressure=50
# Add to restarts
sudo vi /etc/sysctl.conf
# Add this line to /etc/sysctl.conf
vm.vfs_cache_pressure = 50
Configuring a Firewall
UFW (uncomplicated firewall) is a tool that simplifies configuring of iptables
to secure your server. It is an easy to use firewall.
Install ufw (most likely already installed)
sudo apt-get install ufw
Enable IPv6: Edit /etc/default/ufw
and set IPv6=yes
sudo vi /etc/default/ufw
# set
IPv6=yes
Check ufw status and rules (will be inactive if not yet enabled).
sudo ufw status verbose
Setup default firewall policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow common connections only allow what you use
Allowing SSH connection (default ssh port 22)
sudo ufw allow ssh
# same as: sudo ufw allow 22/tcp
If you are using different port for SSH (see SSH Lockdown: custom port above), then allow that instead of port 22
sudo ufw allow 2222/tcp
Allow HTTP and HTTPS
sudo ufw allow http
sudo ufw allow https
# the above two commands are the same as
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Allow SMTP - if sending out mail directly using SMTP
sudo ufw allow 25/tcp
Check all added exceptions (allowed connections)
sudo ufw show added
Enable UFW: Also useful when reloading to update configuration
sudo ufw disable
sudo ufw enable
Reset UFW
In case of errors during configuration, you can reset ufw
with the following command
sudo ufw reset
UFW and docker: Docker usually manipulates iptables
rules meaning that the ufw restrictions will not apply.
Here are a few workarounds I’ve found useful. You can either:
- Only bind docker containers to loopback interfaces e.g.
docker run -p "127.0.0.1:8080:80"
.. - Not use port forwarding (
-p
or-P
) esp. if you don’t need it. - Add
--iptables=false
to the docker daemon: Uncomment the following line in/etc/default/docker
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --iptables=false"
Restart the docker service for the change to effect sudo systemctl restart docker
4. Use the host’s network docker run --net host
..
Intrusion Prevention
CrowdSec (Recommended)
CrowdSec provides community-driven threat intelligence.
Install CrowdSec:
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt update
sudo apt install crowdsec
CrowdSec automatically detects services and shares threat data while maintaining privacy.
Start and enable CrowdSec:
sudo systemctl enable --now crowdsec
Install the firewall bouncer to automatically block detected IPs:
sudo apt install crowdsec-firewall-bouncer-iptables
View current detections and bans:
sudo cscli decisions list
sudo cscli alerts list
CrowdSec provides community threat intelligence while fail2ban works with local patterns.
Key Advantages
Community Intelligence:
- Each instance contributes to global threat detection
- Shared indicators updated in real-time
- Collective learning from attack patterns
Machine Learning:
- Identifies behavioral patterns regex-based systems miss
- Adapts to new attack vectors automatically
- Reduces false positives through smart analysis
API Integration:
- Works with CDNs (Cloudflare, AWS WAF)
- Integrates with cloud firewalls
- Supports custom bouncers for any platform
Privacy-First:
- Shares hashed indicators, not raw IPs
- No personal data transmission
- Local decision making with global intelligence
Migration from fail2ban
If you have fail2ban installed:
# Stop and disable fail2ban
sudo systemctl stop fail2ban
sudo systemctl disable fail2ban
# CrowdSec automatically handles similar scenarios
# No complex jail configuration needed
Fail2ban (Alternative)
For environments that prefer fail2ban:
sudo apt install fail2ban
Basic SSH protection in /etc/fail2ban/jail.local
:
[DEFAULT]
bantime = 1800
findtime = 600
maxretry = 3
ignoreip = 127.0.0.1/8
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
Enable and start:
sudo systemctl enable --now fail2ban
Note: While both solutions help reduce invalid login attempts, they don’t eliminate the need for strong authentication. Use SSH key authentication as described above.
Monitoring
Quick Setup with Netdata
One-line installation for immediate system insights:
bash <(curl -Ss https://my-netdata.io/kickstart.sh)
Access at http://your-server:19999
- no configuration needed.
What you get:
- Real-time system metrics (CPU, memory, disk, network)
- Application monitoring (web servers, databases)
- Alert notifications
- Beautiful, responsive web interface
Configure firewall for Netdata:
sudo ufw allow 19999/tcp
Use Prometheus + Grafana for custom dashboards, metrics collection, and advanced alerting.
Setup Process
Install Prometheus:
sudo useradd --no-create-home --shell /bin/false prometheus
sudo mkdir /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /etc/prometheus /var/lib/prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.40.0/prometheus-2.40.0.linux-amd64.tar.gz
tar xvfz prometheus-2.40.0.linux-amd64.tar.gz
sudo cp prometheus-2.40.0.linux-amd64/prometheus /usr/local/bin/
sudo cp prometheus-2.40.0.linux-amd64/promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtool
Basic Prometheus config in /etc/prometheus/prometheus.yml
:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
Install node_exporter for system metrics:
wget https://github.com/prometheus/node_exporter/releases/download/v1.4.0/node_exporter-1.4.0.linux-amd64.tar.gz
tar xvfz node_exporter-1.4.0.linux-amd64.tar.gz
sudo cp node_exporter-1.4.0.linux-amd64/node_exporter /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false node_exporter
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter
Create systemd services and enable them. For detailed setup, see Prometheus documentation.
Relevant Guides & Tutorials
Helpful resources for further reading and advanced configurations:
Official Documentation
Comprehensive Security Guides
- Ubuntu System Hardening Guide - Linux Audit
- 40 Linux Server Hardening Security Tips - nixCraft
- Ubuntu 24.04 LTS Initial Setup & Hardening - Community Gist
DigitalOcean Community Tutorials
- Initial Server Setup with Ubuntu
- How to Set Up a Firewall with UFW on Ubuntu
- UFW Essentials: Common Firewall Rules and Commands
Modern Security Practices
- CrowdSec Documentation - Community-driven intrusion prevention
- Caddy Documentation - Automatic HTTPS web server
- Docker Security Best Practices - Official Docker security guide