So you just purchased your first VPS (Virtual Private Server). Exciting times! But now you're staring at a blank terminal wondering, "What do I do next?"
I've been there. When I set up my first VPS years ago, I made every mistake in the bookβleft root login enabled, forgot to configure the firewall, and woke up to a compromised server within 48 hours. Not fun.
This guide is everything I wish someone had given me back then. We'll walk through every step together, from your very first SSH login to running a production-ready, secure Node.js application.
What Exactly is a VPS?
Think of a VPS as your own little piece of a powerful server. It's like renting an apartment in a big buildingβyou share the physical structure, but your space is completely private and isolated. You get full control over your environment, can install whatever software you want, and (most importantly) you're responsible for keeping it secure.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Physical Server β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β VPS #1 β β VPS #2 β β VPS #3 β β
β β (Your VPS) β β (Someone β β (Another β β
β β β β else's) β β user's) β β
β β βββββββββ β β βββββββββ β β βββββββββ β β
β β β Your β β β β Their β β β β Their β β β
β β β Apps β β β β Apps β β β β Apps β β β
β β βββββββββ β β βββββββββ β β βββββββββ β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWhy Initial Setup Matters (A Lot)
Here's a reality check: the moment your VPS goes live, it becomes a target. Automated bots are constantly scanning IP addresses, looking for easy preyβservers with default configurations, weak passwords, or no firewall.
Within minutes of spinning up a new VPS, you'll start seeing login attempts in your logs. I'm not exaggerating. The internet is a wild place.
Common Beginner Mistakes
- Using root for everything β One wrong command can nuke your system
- Password-only SSH β Easily brute-forced by bots
- No firewall β Leaving all ports wide open
- Skipping updates β Known vulnerabilities remain exploitable
- No backups β One disk failure = total data loss
What You'll Achieve By The End
By the time you finish this guide, your VPS will have:
- A secure, non-root user account
- SSH key authentication (with password login disabled)
- A properly configured firewall
- Brute-force protection with Fail2Ban
- Nginx as a reverse proxy
- Node.js apps running with PM2
- SSL/HTTPS with Let's Encrypt
- A backup strategy
First Login & Server Basics
Alright, let's dive in! You should have received an email from your VPS provider with your server's IP address, username (usually root), and a temporary password. Let's use them.
Connecting via SSH
Open your terminal (or PowerShell on Windows) and type:
ssh root@your_server_ipReplace your_server_ip with the actual IP address. If this is your first time connecting, you'll see a message about the authenticity of the host. Type yes to continue.
ββββββββββββββββ SSH (Port 22) ββββββββββββββββ
β β ββββββββββββββββββββββββββββββΆ β β
β Your Local β β Your VPS β
β Computer β ββββββββββββββββββββββββββββββ β (Remote) β
β β Encrypted Connection β β
ββββββββββββββββ ββββββββββββββββUnderstanding the Root User
You just logged in as rootβthe superuser with unlimited power. While this sounds cool, it's actually dangerous for everyday use. One typo in a command like rm -rf / and your entire system is gone. No confirmation, no undo.
We'll create a safer user account shortly. For now, let's do some initial housekeeping.
Check Your OS Version
# Check Ubuntu version
lsb_release -a
# Or use this command
cat /etc/os-releaseYou should see something like "Ubuntu 22.04 LTS" or "Ubuntu 24.04 LTS". This guide is optimized for Ubuntu, but most commands work on Debian too.
Update System Packages
First things firstβupdate everything. Your VPS might be running outdated software with known security vulnerabilities.
# Update package lists
sudo apt update
# Upgrade all installed packages
sudo apt upgrade -y
# Remove unnecessary packages
sudo apt autoremove -yThe -y flag automatically answers "yes" to prompts. Useful for scripting, but when learning, you might want to omit it so you can see what's being installed.
Set Your Timezone
Proper timezone settings help with log analysis and scheduled tasks.
# Check current timezone
timedatectl
# List available timezones (search for yours)
timedatectl list-timezones | grep Asia
# Set timezone (example: Asia/Kolkata for India)
sudo timedatectl set-timezone Asia/Kolkata
# Verify the change
dateWhen you're debugging issues at 2 AM, you'll want log timestamps to match your local time. It also matters for cron jobs and scheduled backups.
Now You've successfully:
- Connected to your VPS via SSH
- Verified your OS version
- Updated all system packages
- Set the correct timezone
Create a Non-Root User
Remember what I said about root being dangerous? Let's fix that by creating a regular user account with sudo (admin) privileges. You'll use this for daily tasks.
Why Not Use Root?
- No safety net: Root can delete anything, anywhere, instantly
- Audit trail: With sudo, every privileged command gets logged
- Principle of least privilege: Only elevate permissions when needed
- Attack surface: If attackers crack your root password, game over
Create the New User
# Create a new user (replace 'jignesh' with your preferred username)
adduser jigneshYou'll be prompted to set a password and fill in some optional information. Use a strong passwordβbut don't stress too much, we'll be switching to SSH keys soon.
Add User to Sudo Group
# Grant sudo privileges
usermod -aG sudo jigneshThe -aG flags mean "append to group"βthis adds the user to the sudo group without removing them from other groups.
Test the New User
Before we proceed, let's make sure everything works. Open a new terminal window (keep the root session open just in case) and try:
# Connect as your new user
ssh jignesh@your_server_ip
# Test sudo access
sudo whoamiIf the second command returns root, congratulations! Your user has sudo powers.
Always test your new user's SSH and sudo access in a separate terminal before closing your root session. If something's wrong, you can still fix it as root.
Quick Verification Commands
# Check which groups you belong to
groups jignesh
# Should show: jignesh sudo
# Check sudo configuration
sudo cat /etc/sudoers.d/*You've created a secure non-root user. From now on, use this account for all your work. You'll use sudo when you need elevated permissions.
Secure SSH Access
This is arguably the most important step in this entire guide. SSH is the front door to your server, and by default, it's using a pretty weak lock. Let's upgrade to bank-vault security.
Before (Vulnerable):
ββββββββββββ Password: "admin123" ββββββββββββ
β Attacker β ββββββββββββββββββββββββΆ β VPS β
ββββββββββββ Easy to crack ββββββββββββ
After (Secure):
ββββββββββββ SSH Key Required ββββββββββββ
β Attacker β βββββββββββββ³βββββββββββΆ β VPS β
ββββββββββββ No key? No entry! ββββββββββββStep 3.1: Generate SSH Keys (On Your Local Machine)
First, let's create an SSH key pair on your local computer. Open a terminal on your local machine (not the VPS):
# Generate a new SSH key pair
ssh-keygen -t ed25519 -C "your_email@example.com"
# You'll be asked where to save it - press Enter for default location
# Then enter a passphrase (recommended) or leave emptyThis creates two files:
~/.ssh/id_ed25519β Your private key (NEVER share this!)~/.ssh/id_ed25519.pubβ Your public key (safe to share)
Why ed25519?
It's the most secure and modern algorithm available. Faster and more secure than RSA. If you're on an older system that doesn't support it, use ssh-keygen -t rsa -b 4096 instead.
Step 3.2: Copy Public Key to Server
Now let's put your public key on the server. Still on your local machine:
# The easy way (if ssh-copy-id is available)
ssh-copy-id jignesh@your_server_ip
# Or manually:
cat ~/.ssh/id_ed25519.pub | ssh jignesh@your_server_ip "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"Step 3.3: Test Key-Based Login
Before we disable password login, make sure key auth works:
# Open a NEW terminal and try connecting
ssh jignesh@your_server_ip
# If it doesn't ask for a password, keys are working!Critical Checkpoint
Do NOT proceed to the next step unless SSH key login is working. If you disable password login without working keys, you'll be permanently locked out!
Step 3.4: Harden SSH Configuration
Now let's lock down SSH. Connect to your server and edit the SSH config:
# Backup the original config first
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Edit the SSH configuration
sudo nano /etc/ssh/sshd_configFind and modify these lines (or add them if they don't exist):
/etc/ssh/sshd_config
# Change default port (optional but recommended)
Port 2222
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication no
# Disable empty passwords
PermitEmptyPasswords no
# Enable public key authentication
PubkeyAuthentication yes
# Limit max authentication attempts
MaxAuthTries 3
# Disable X11 forwarding (unless you need it)
X11Forwarding no
# Set login grace time (time to enter password)
LoginGraceTime 60
# Only allow specific users (optional)
AllowUsers jigneshStep 3.5: Restart SSH Safely
Before restarting SSH, let's make sure we don't lock ourselves out:
# Test the configuration for syntax errors
sudo sshd -t
# If no errors, restart SSH
sudo systemctl restart sshdKeep Your Current Session Open!
Don't close your current SSH session. Open a new terminal and test the new configuration. If you changed the port, remember to use it:
Test new connection (with new port)
ssh -p 2222 jignesh@your_server_ipQuick Reference: SSH Config Location
# Server-side SSH config
/etc/ssh/sshd_config
# Your local SSH config (for easier connections)
~/.ssh/configYou can create a local config to make connecting easier:
~/.ssh/config (on your local machine)
Host myserver
HostName your_server_ip
User jignesh
Port 2222
IdentityFile ~/.ssh/id_ed25519Now you can simply type ssh myserver to connect!
Step 3 Complete! β
Your SSH is now significantly more secure:
- Key-based authentication only
- Root login disabled
- Custom port (optional)
- Limited authentication attempts
Basic Firewall Setup (UFW)
Think of a firewall as a bouncer at a club. Without one, anyone can walk in through any door. With UFW (Uncomplicated Firewall), you decide exactly which doors are open and who gets through.
INTERNET
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββ
β FIREWALL (UFW) β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β Port 22 β β Port 80 β β Port 443 β β
β β (SSH) β β (HTTP) β β (HTTPS) β β
β β β β β β β β β β β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β
β All other ports: β BLOCKED β
ββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
YOUR SERVERWhy You Need a Firewall
By default, your VPS might have all 65,535 ports potentially accessible. That's 65,535 potential entry points for attackers. A firewall lets you:
- Block all incoming traffic by default
- Whitelist only the ports you need (SSH, HTTP, HTTPS)
- Protect internal services from external access
- Add an extra layer of defense
Install and Configure UFW
# Install UFW (usually pre-installed on Ubuntu)
sudo apt install ufw -y
# Check current status
sudo ufw statusSet Up Default Policies
# Deny all incoming connections by default
sudo ufw default deny incoming
# Allow all outgoing connections by default
sudo ufw default allow outgoingAllow Essential Services
Now let's open the ports we need. Order matters here!
# Allow SSH (CRITICAL - do this FIRST!)
# If you changed SSH port to 2222:
sudo ufw allow 2222/tcp
# Or if using default port 22:
sudo ufw allow ssh
# Allow HTTP (for web traffic)
sudo ufw allow 80/tcp
# Allow HTTPS (for secure web traffic)
sudo ufw allow 443/tcpAlways allow your SSH port before enabling UFW. If you enable UFW without allowing SSH, you'll be immediately locked out of your server!
Enable the Firewall
# Enable UFW (will ask for confirmation)
sudo ufw enable
# Check status and rules
sudo ufw status verboseYou should see output like:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
2222/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
2222/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)Useful UFW Commands
# View numbered rules
sudo ufw status numbered
# Delete a rule by number
sudo ufw delete 3
# Delete a rule by specification
sudo ufw delete allow 8080/tcp
# Allow connections from specific IP only
sudo ufw allow from 192.168.1.100
# Allow specific port from specific IP
sudo ufw allow from 192.168.1.100 to any port 3306
# Reload firewall rules
sudo ufw reload
# Disable firewall (not recommended)
sudo ufw disableUFW can limit connection attempts to prevent brute-force attacks:
# Limit SSH connections (6 attempts per 30 seconds)
sudo ufw limit 2222/tcpYour firewall is now active, allowing only:
- SSH (port 2222 or 22)
- HTTP (port 80)
- HTTPS (port 443)
All other ports are blocked by default.
Production-Ready Setup
Now for the exciting partβsetting up your server to actually run applications! We'll install Nginx as a reverse proxy, Node.js for your backend, and PM2 to keep everything running.
INTERNET
β
βΌ
βββββββββββββ
β Nginx β (Port 80/443)
β (Proxy) β
βββββββ¬ββββββ
β
ββββββββββββββββββΌβββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ
β App 1 β β App 2 β β App 3 β
β :3000 β β :3001 β β :3002 β
βββββββββββ βββββββββββ βββββββββββ
β β β
ββββββββββββββββββΌβββββββββββββββββ
β
βββββββββββ
β PM2 β (Process Manager)
βββββββββββInstall Nginx
# Install Nginx
sudo apt install nginx -y
# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
# Check status
sudo systemctl status nginxOpen your browser and navigate to your server's IP. You should see the default Nginx welcome page!
Install Node.js (Using NVM)
I recommend using NVM (Node Version Manager) instead of apt. It gives you more control over Node versions:
# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Reload shell configuration
source ~/.bashrc
# Install latest LTS version of Node.js
nvm install --lts
# Verify installation
node --version
npm --versionInstall PM2 (Process Manager)
PM2 keeps your Node.js apps running forever, restarts them if they crash, and manages logs:
# Install PM2 globally
npm install -g pm2
# Verify installation
pm2 --versionSet Up Directory Structure
Let's create a clean structure for your applications:
# Create app directories
sudo mkdir -p /var/www/myapp
sudo chown -R $USER:$USER /var/www/myapp
# Create a simple test app
nano /var/www/myapp/app.js/var/www/myapp/app.js
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello from your VPS!');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});Run App with PM2
# Start the app with PM2
pm2 start /var/www/myapp/app.js --name "myapp"
# View running processes
pm2 list
# View logs
pm2 logs myapp
# Set up PM2 to start on boot
pm2 startup
# Save current process list
pm2 saveConfigure Nginx as Reverse Proxy
Now let's route traffic from Nginx to your Node.js app:
# Create Nginx config for your app
sudo nano /etc/nginx/sites-available/myapp/etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Or use your server IP: server_name 123.456.789.0;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
}# Enable the site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test Nginx configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginxEnvironment Variables
Store sensitive config in environment variables, not in code:
# Create environment file
nano /var/www/myapp/.env/var/www/myapp/.env
NODE_ENV=production
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=your-super-secret-key
API_KEY=your-api-key# Secure the file
chmod 600 /var/www/myapp/.env
# Start PM2 with env file
pm2 start app.js --name "myapp" --env productionUseful PM2 Commands
# Restart app
pm2 restart myapp
# Stop app
pm2 stop myapp
# Delete from PM2
pm2 delete myapp
# Monitor all apps
pm2 monit
# View logs with timestamps
pm2 logs --timestampYour production stack is ready:
- Nginx serving as reverse proxy
- Node.js installed via NVM
- PM2 managing your processes
- Environment variables secured
Advanced Security (Recommended)
Your server is secure, but let's take it a step further. These additions will protect against more sophisticated attacks.
SSL/HTTPS with Let's Encrypt
Free SSL certificates! This is essential for production.
Before SSL:
ββββββββββ HTTP (unencrypted) ββββββββββ
β Client β ββββββββββββββββββββββΆ β Server β
ββββββββββ Anyone can read! ββββββββββ
After SSL:
ββββββββββ HTTPS (encrypted) ββββββββββ
β Client β ββββββββββββββββββββββΆ β Server β
ββββββββββ π Secure! ββββββββββ# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Get SSL certificate (replace with your domain)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Test auto-renewal
sudo certbot renew --dry-runCertbot will automatically modify your Nginx config to use HTTPS and redirect HTTP traffic.
Wrapping Up
Congratulations! You've gone from a blank VPS to a secure, production-ready server. That's a huge accomplishment.
Your server is now significantly more secure than 90% of VPS instances out there. But security is a journey, not a destination. Stay curious, keep learning, and keep your systems updated.







Leave a Comment
Share Your Thoughts