Introduction
As an indie hacker, I often need to quickly set up environments for new app ideas while keeping costs low. I developed a simple, budget-friendly approach that lets me deploy projects efficiently without sacrificing security or scalability. In this guide, I’ll walk you through the steps to set up a secure VPS, covering everything from basic SSH configuration to deploying essential services like Nginx, Docker, and SSL.
For sake of simplicity, I'll assume:
- you have a VPS (I use hetzner's
CX22
with 4GB of ram and 40GB of NVMe SSD, but you can use anything else) - your app's domain is
example.com
- your private and public keys file names are
example
andexample.pub
- your server ip address is
1.1.1.1
- your email is
your-email@email.com
- your server user name is
new_user_name
Don't worry, this will all make sense soon.
1. Connect to Your VPS
Once you have your server up and running, the first step is to connect to it using SSH.
ssh root@<server_ip_address>
2. Run System Updates
It’s important to make sure your server is running the latest updates for security and performance. Run the following commands to update and upgrade all packages.
sudo apt-get update && sudo apt-get upgrade
This will fetch and install the latest updates for your system packages.
3. Create a New User with Sudo Privileges
sudo adduser <new_user_name>
sudo usermod -aG sudo <new_user_name>
This will allow the new user to perform administrative tasks when needed.
4. Set Up SSH for New User
Switch to the new user:
su <new_user_name>
On your local machine, generate a new SSH key pair (if you don’t have one already):
ssh-keygen -t ed25519 -C "your-email@email.com"
Save it with a name you can remember as you'll need it soon. For example purposes, lets call it "example", as mentioned in the beginning.
On the VPS, create the .ssh directory and paste your public key:
mkdir ~/.ssh && mkdir ~/.ssh/authorized_keys
Back at your local machine, assuming your public key is at ~/.ssh/example.pub
:
cat ~/.ssh/example.pub | ssh <new_user_name>@<server_ip_address> 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'```
5. Disable Root Login and Password Authentication
For added security, you should disable root login and password-based authentication.
Provide the correct permissions to your server's .ssh folder:
chmod 755 ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Then, edit the SSH configuration:
sudo vim /etc/ssh/sshd_config
Set the following configurations to disable root login and password authentication:
PubkeyAuthentication yes
PermitRootLogin no
PasswordAuthentication no
Edit your host SSH config
vim ~/.ssh/config
Host 1.1.1.1 # your server's ip here
HostName 1.1.1.1 # your server's ip here
User your_user
IdentityFile ~/example # your public key file (example.pub)
6. Enable Firewall (UFW)
Enable UFW to allow only specific traffic:
sudo apt install ufw
sudo ufw enable
sudo ufw allow OpenSSH
sudo ufw allow 22
This ensures that SSH traffic is allowed while securing other ports.
7. Install Fail2Ban for Brute Force Protection
Fail2Ban helps protect your server from brute force attacks. Install it and configure:
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo vim /etc/fail2ban/jail.local
Ensure SSHD jail is enabled:
[sshd]
enabled = true
8. Install and Configure Nginx
Install Nginx as your web server:
sudo apt-get install nginx
sudo ufw allow 'Nginx Full'
Then, configure it to act as a reverse proxy for your app. Open the configuration file:
sudo vim /etc/nginx/sites-available/default
Replace the contents with:
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:3000; # Replace 3000 with the port your app is running on
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Replace example.com with your actual domain name.
The proxy_pass line should point to the port your application is running on (e.g., port 3000 for a Node.js app).
9. Enable Nginx
To ensure everything is configured properly, test the configuration and restart Nginx:
sudo nginx -t
sudo systemctl restart nginx
10. Setup SSL with Certbot
Certbot will automatically configure SSL for your domain and generate the certificates.
Install Certbot and the Nginx plugin:
sudo apt update
sudo apt install certbot python3-certbot-nginx
Note: Before running this step, you need to set you domains and point them to your server's ip (using 1.1.1.1 as an example). If you just did it, wait a few minutes before moving to the next step so that DNS can broadcast.
Type | Host | Answer | TTL | Priority | Options |
---|---|---|---|---|---|
A | example.com |
1.1.1.1 | 600 | ||
A | www.example.com |
1.1.1.1 | 600 | ||
CNAME | *.example.com |
1.1.1.1 | 600 |
Run Certbot for Nginx
sudo certbot --nginx
When prompted: Enter your email address (for renewal and security notices). Agree to the terms of service. Select the domain(s) you want to enable SSL for (e.g., example.com and www.example.com).
Verify the Nginx Configuration:
sudo vim /etc/nginx/sites-available/default
It should look similar to this:
server {
listen 443 ssl; # managed by Certbot
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
# Recommended security settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384";
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
if ($host = www.example.com) {
return 301 https://$host$request_uri;
}
if ($host = example.com) {
return 301 https://$host$request_uri;
}
listen 80;
server_name example.com www.example.com;
return 404; # managed by Certbot
}
(Optional) - Force Https
If Certbot didn’t already set up HTTP-to-HTTPS redirection, you can do this manually by modifying your server block for port 80 to redirect all traffic to HTTPS:
server {
listen 80;
server_name example.com www.example.com;
location / {
return 301 https://$host$request_uri;
}
}
Test SSL and Reload Nginx:
Certbot should automatically reload Nginx after successful installation, but you can test your configuration and reload Nginx manually just to ensure everything is working.
bash
sudo nginx -t
sudo systemctl reload nginx
Test automatic renewal:
Let’s Encrypt certificates are valid for 90 days, but Certbot will automatically renew them for you. To make sure it’s working, you can test the renewal process:
sudo certbot renew --dry-run
11. Install Docker
Update and install prerequisites
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
Add Docker GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker
sudo apt update
sudo apt install docker-ce
Verify Docker installation
sudo docker --version
sudo docker run hello-world
(Optional) Add current user to Docker group
sudo usermod -aG docker ${USER}
newgrp docker
Enable Docker at startup
sudo systemctl enable docker
12. Set Up Unattended Upgrades
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades
Now your VPS is fully secured and configured with essential services like Nginx, SSL, and Docker. You’ve also set up SSH key-based authentication, a firewall, and brute-force protection with Fail2Ban.
In the next post, I'll teach you how to integrate this deployment strategy with a CI/CD pipeline so you can manage your code remotely.
Happy deploying!
Diogo