For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/javascript/beginners-guide/12-deploy-vps-nginx.md.

Deploy your website to a Virtual Private Server - server setup, SSH, nginx configuration, HTTPS with Let's Encrypt, and basic security hardening.

Deploying to a VPS with Nginx

You have a working website. Now let us put it on the internet. This chapter walks through the entire process: getting a server, setting it up, configuring nginx to serve your files, and securing the site with HTTPS.

What is a VPS?

A Virtual Private Server (VPS) is a virtual machine running in a data center. You get root access to a full Linux server that runs 24/7. Unlike shared hosting, you control everything - the operating system, the software, the firewall.

Choosing a provider

Popular VPS providers:

ProviderStarting priceNotes
Hetzner~€4/monthExcellent value, EU and US data centers
DigitalOcean$6/monthSimple interface, good documentation
Linode (Akamai)$5/monthSolid reliability, good support
Vultr$6/monthMany locations worldwide

For a simple static website, the cheapest plan from any provider is more than enough. This guide uses **Ubuntu 22.04 LTS ** as the operating system - choose it when creating your server.

What you need before starting

  1. A VPS running Ubuntu 22.04 LTS (or newer)
  2. A domain name (optional but recommended - e.g., yoursite.com)
  3. The website files from the previous chapter
  4. A terminal on your local machine

Step 1: initial server setup

When you create a VPS, the provider gives you the server's IP address and a root password (or lets you add an SSH key).

Connect to your server

ssh root@YOUR_SERVER_IP

Replace YOUR_SERVER_IP with the actual IP (e.g., 203.0.113.42). On the first connection, you will be asked to confirm the server's fingerprint - type yes.

Update the system

apt update && apt upgrade -y

Create a non-root user

Running as root is dangerous - a mistake can destroy the entire system. Create a regular user:

adduser deploy

You will be prompted to set a password and fill in optional details (you can skip the details by pressing Enter).

Grant the user sudo privileges:

usermod -aG sudo deploy

Set up SSH key authentication

SSH keys are more secure than passwords. On your local machine (not the server), generate a key pair if you do not already have one:

ssh-keygen -t ed25519 -C "[email protected]"

Press Enter to accept the default file location. Set a passphrase for extra security.

Copy the public key to the server:

ssh-copy-id deploy@YOUR_SERVER_IP

Test the connection:

ssh deploy@YOUR_SERVER_IP

You should log in without being asked for a password.

Disable password authentication

Once SSH keys work, disable password login for security. On the server, edit the SSH configuration:

sudo nano /etc/ssh/sshd_config

Find and change these lines:

PasswordAuthentication no
PermitRootLogin no

Restart the SSH service:

sudo systemctl restart sshd

Warning: Make sure your SSH key login works before doing this. If you lock yourself out, you will need to use the provider's console access.

Step 2: set up the firewall

Ubuntu comes with ufw (Uncomplicated Firewall):

# Allow SSH
sudo ufw allow OpenSSH

# Allow HTTP and HTTPS
sudo ufw allow 'Nginx Full'

# Enable the firewall
sudo ufw enable

# Check status
sudo ufw status

Result:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)

Step 3: install nginx

sudo apt install nginx -y

Verify it is running:

sudo systemctl status nginx

Open your browser and navigate to http://YOUR_SERVER_IP. You should see the default nginx welcome page.

How nginx works

Nginx is a web server - it listens for HTTP requests and serves files in response. For a static website, nginx simply serves your HTML, CSS, and JavaScript files directly to the browser.

The configuration lives in /etc/nginx/. Key paths:

PathPurpose
/etc/nginx/nginx.confMain configuration
/etc/nginx/sites-available/Site configurations (available)
/etc/nginx/sites-enabled/Site configurations (active - symlinks)
/var/www/Convention for website files

Step 4: upload your website files

Create a directory for your site on the server:

sudo mkdir -p /var/www/mysite
sudo chown deploy:deploy /var/www/mysite

From your local machine, upload the files using rsync:

rsync -avz --delete ./my-website/ deploy@YOUR_SERVER_IP:/var/www/mysite/

Breakdown:

  • -a - archive mode (preserves permissions, timestamps)
  • -v - verbose output
  • -z - compress during transfer
  • --delete - remove files on the server that no longer exist locally

Alternative using scp:

scp -r ./my-website/* deploy@YOUR_SERVER_IP:/var/www/mysite/

Verify the files are there:

ssh deploy@YOUR_SERVER_IP "ls -la /var/www/mysite/"

Result:

total 24
drwxr-xr-x 5 deploy deploy 4096 Jan 15 10:00 .
drwxr-xr-x 3 root   root   4096 Jan 15 09:55 ..
drwxr-xr-x 2 deploy deploy 4096 Jan 15 10:00 css
drwxr-xr-x 2 deploy deploy 4096 Jan 15 10:00 data
-rw-r--r-- 1 deploy deploy 2048 Jan 15 10:00 index.html
drwxr-xr-x 2 deploy deploy 4096 Jan 15 10:00 js
-rw-r--r-- 1 deploy deploy 1536 Jan 15 10:00 projects.html
-rw-r--r-- 1 deploy deploy 1280 Jan 15 10:00 contact.html

Step 5: configure nginx

Create a new site configuration:

sudo nano /etc/nginx/sites-available/mysite

Paste this configuration:

server {
    listen 80;
    listen [::]:80;

    server_name YOUR_DOMAIN_OR_IP;

    root /var/www/mysite;
    index index.html;

    # Serve static files directly
    location / {
        try_files $uri $uri/ =404;
    }

    # Cache static assets
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
        expires 7d;
        add_header Cache-Control "public, no-transform";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Deny access to hidden files
    location ~ /\. {
        deny all;
    }

    # Custom error pages (optional)
    error_page 404 /404.html;
}

Replace YOUR_DOMAIN_OR_IP with your domain (e.g., yoursite.com) or your server's IP address.

Enable the site

# Create a symlink to sites-enabled
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/

# Remove the default site (optional)
sudo rm /etc/nginx/sites-enabled/default

# Test the configuration
sudo nginx -t

Result:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload nginx:

sudo systemctl reload nginx

Open http://YOUR_DOMAIN_OR_IP in your browser. Your website is live.

Step 6: set up a domain (optional)

If you have a domain name, point it to your server:

  1. Go to your domain registrar's DNS settings
  2. Add an A record pointing to your server's IP:
TypeNameValueTTL
A@YOUR_SERVER_IP300
AwwwYOUR_SERVER_IP300

DNS changes can take a few minutes to a few hours to propagate. You can check with:

dig +short yoursite.com

Once the domain resolves to your server IP, update the nginx config:

server_name yoursite.com www.yoursite.com;

Reload nginx:

sudo nginx -t && sudo systemctl reload nginx

Step 7: HTTPS with Let's Encrypt

HTTPS is mandatory for any website in production. Let's Encrypt provides free SSL certificates.

Install Certbot

sudo apt install certbot python3-certbot-nginx -y

Obtain and install a certificate

sudo certbot --nginx -d yoursite.com -d www.yoursite.com

Certbot will:

  1. Verify you own the domain
  2. Obtain a certificate
  3. Automatically update your nginx config to use HTTPS
  4. Set up HTTP-to-HTTPS redirect

Follow the prompts - enter your email and agree to the terms.

Verify HTTPS

Open https://yoursite.com in your browser. You should see the lock icon.

Automatic renewal

Let's Encrypt certificates expire every 90 days. Certbot sets up automatic renewal:

# Test the renewal process
sudo certbot renew --dry-run

If the dry run succeeds, renewals will happen automatically via a systemd timer.

Verify the timer is active:

sudo systemctl status certbot.timer

Step 8: basic security hardening

Automatic security updates

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

Select "Yes" when asked to enable automatic updates.

Fail2Ban - block brute-force attacks

sudo apt install fail2ban -y

Create a local configuration:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log

Start and enable:

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Change the SSH port (optional)

Changing the default SSH port (22) reduces automated login attempts:

sudo nano /etc/ssh/sshd_config

Change Port 22 to another port (e.g., Port 2222):

Port 2222

Update the firewall:

sudo ufw allow 2222/tcp
sudo ufw delete allow OpenSSH
sudo systemctl restart sshd

Now connect with:

ssh -p 2222 deploy@YOUR_SERVER_IP

Deploying updates

Whenever you update your website locally, deploy the changes with a single command:

rsync -avz --delete ./my-website/ deploy@YOUR_SERVER_IP:/var/www/mysite/

No server restart needed - nginx serves files directly from disk.

A simple deploy script

Create deploy.sh in your project root:

#!/bin/bash
set -euo pipefail

SERVER="deploy@YOUR_SERVER_IP"
REMOTE_PATH="/var/www/mysite"

echo "Deploying to $SERVER:$REMOTE_PATH..."

rsync -avz --delete \
    --exclude '.git' \
    --exclude 'deploy.sh' \
    --exclude '.DS_Store' \
    ./ "$SERVER:$REMOTE_PATH/"

echo "Deployment complete!"

Make it executable and run it:

chmod +x deploy.sh
./deploy.sh

Verifying the deployment

After deploying, check these things:

  1. Site loads: Open your domain in a browser - all pages should work.
  2. HTTPS works: The lock icon should appear in the address bar.
  3. No mixed content: Open the browser console and check for warnings about HTTP resources on an HTTPS page.
  4. Performance: Open Chrome DevTools → Network tab, reload the page, and verify static assets have cache headers.
  5. Mobile: Test on a phone or use the responsive mode in dev tools.

Quick checks from the command line

# Check if the site responds
curl -I https://yoursite.com

# Check the SSL certificate
curl -vI https://yoursite.com 2>&1 | grep -i "subject\|expire"

# Check security headers
curl -I https://yoursite.com 2>&1 | grep -i "x-frame\|x-content\|referrer"

Complete server setup checklist

Here is everything we did, in order:

  • Created a VPS with Ubuntu 22.04 LTS
  • Updated the system (apt update && apt upgrade)
  • Created a non-root user with sudo
  • Set up SSH key authentication
  • Disabled password authentication
  • Configured the firewall with ufw
  • Installed nginx
  • Uploaded website files
  • Created nginx site configuration
  • Pointed the domain to the server (DNS)
  • Installed HTTPS with Let's Encrypt
  • Set up automatic security updates
  • Installed Fail2Ban
  • Created a deploy script

What comes next

You now have a website live on the internet, served by nginx over HTTPS. The next chapter introduces TypeScript - static types for JavaScript that catch bugs before they reach production.

Continue with: TypeScript

After that, you could:

  • Add a CI/CD pipeline - automatically deploy when you push to Git
  • Add a backend - use Node.js with Express behind nginx as a reverse proxy
  • Add monitoring - use tools like htop, nginx access logs, or services like UptimeRobot
  • Add a database - SQLite for small projects, PostgreSQL for larger ones
  • Learn Docker - containerize your application for easier deployment
  • Explore frameworks - React, Vue, Svelte, or Next.js for more complex applications

Summary

  • A VPS gives you a full Linux server in the cloud.
  • SSH keys are more secure than passwords - disable password authentication.
  • nginx serves static files efficiently with caching and security headers.
  • Let's Encrypt provides free HTTPS certificates with automatic renewal.
  • Fail2Ban and unattended-upgrades protect against common threats.
  • Deploy updates with a single rsync command.

Congratulations - you have gone from writing your first console.log("Hello, world!") to deploying a live website. The fundamentals you have learned (variables, functions, arrays, objects, DOM, events, fetch, deployment) are the foundation for everything else in web development. Keep building.