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
CX22with 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
exampleandexample.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 upgradeThis 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_keysBack 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_keysThen, edit the SSH configuration:
sudo vim /etc/ssh/sshd_configSet the following configurations to disable root login and password authentication:
PubkeyAuthentication yes
PermitRootLogin no
PasswordAuthentication noEdit your host SSH config
vim ~/.ssh/configHost 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 22This 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.localEnsure SSHD jail is enabled:
[sshd]
enabled = true8. 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/defaultReplace 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 nginx10. 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-nginxNote: 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 --nginxWhen 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/defaultIt 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-run11. Install Docker
Update and install prerequisites
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-commonAdd Docker GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgAdd 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/nullInstall Docker
sudo apt update
sudo apt install docker-ceVerify Docker installation
sudo docker --version
sudo docker run hello-world(Optional) Add current user to Docker group
sudo usermod -aG docker ${USER}
newgrp dockerEnable Docker at startup
sudo systemctl enable docker12. Set Up Unattended Upgrades
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgradesNow 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
