How to Host an Astro Site on a VPS: Complete Beginner's Guide bishal-biswas.github.io | Bishal Biswas

How to Host an Astro Site on a VPS: Complete Beginner's Guide

Table of Content

Introduction

When I started working with modern JavaScript frameworks, I initially used platforms such as Vercel and Netlify because deployment was extremely simple.

Connect the GitHub repository, select the project, and the website goes live.

But sometimes I want more control.

For example, I may have my own VPS and want to host multiple websites on the same server. I may also want to control Nginx, Node.js, PM2, domains, SSL certificates, logs, and deployments myself.

This is where VPS hosting becomes useful.

In this article, I will show you how I host an Astro website on a VPS.

I will use:

  • Ubuntu VPS
  • Node.js
  • npm
  • Astro
  • Nginx
  • PM2
  • Git
  • Let’s Encrypt SSL
  • A custom domain

The process might look complicated initially, but once I understand what each component does, it becomes much easier.


What We Are Going to Build

By the end, the setup will look roughly like this:

User
  |
  | HTTPS
  v
Domain
  |
  v
Nginx
  |
  | Reverse Proxy
  v
Astro Application
  |
  v
Node.js
  |
  v
PM2

For a static Astro website, I can simplify the setup considerably.

For an SSR Astro website, Node.js needs to run the application, so Nginx will forward requests to the Astro server.

In this article, I will focus on the Node.js/SSR-style deployment, because it gives me a setup that can also support dynamic Astro applications.


What Is a VPS?

Before deploying anything, it is useful to understand what a VPS actually is.

VPS stands for Virtual Private Server.

It gives me a virtual server with resources such as:

  • CPU
  • RAM
  • Disk storage
  • Operating system
  • Network access
  • Root or administrative access

Unlike shared hosting, I have much more control over the server.

I can install:

Node.js
Nginx
PM2
MongoDB
PostgreSQL
Docker
Git
and other software

The exact software I need depends on my project.

For an Astro website, I mainly need Node.js, Nginx, and a process manager such as PM2 for a Node-based deployment.


What Do Nginx and PM2 Do?

Two terms often confuse beginners when they first deploy a Node application.

Nginx

Nginx acts as the web server and reverse proxy.

The visitor accesses:

https://example.com

Nginx receives that request.

It can then forward the request to my Astro application running on something like:

http://127.0.0.1:4321

The visitor doesn’t need to know that port 4321 exists.


PM2

PM2 is a process manager for Node.js applications.

If I simply run:

npm run start

the application runs in my terminal.

If I close the terminal or the process crashes, the application can stop.

PM2 helps me keep the Node application running.

It can also:

  • Restart applications
  • Monitor processes
  • Start applications after server reboot
  • Show logs
  • Manage multiple applications

So I can think of it this way:

Nginx = receives web requests

PM2 = keeps my Node application running

Step 1: Connect to the VPS

I normally connect to an Ubuntu VPS using SSH.

From Windows PowerShell or Terminal:

ssh root@YOUR_SERVER_IP

For example:

ssh root@123.123.123.123

Replace the IP address with the actual IP address of your VPS.

If your VPS provider gives you a different username, use that username instead.

For example:

ssh ubuntu@123.123.123.123

Once connected, I am working directly on the server.


Step 2: Update Ubuntu

Before installing anything, I update the package lists.

sudo apt update

Then I can upgrade installed packages:

sudo apt upgrade -y

I usually do this when preparing a new VPS.


Step 3: Install Git

If my Astro project is stored on GitHub, I need Git on the VPS.

sudo apt install git -y

Check that it was installed:

git --version

I should get a version number such as:

git version 2.x.x

Step 4: Install Node.js

Astro requires Node.js.

There are several ways to install Node.js on Ubuntu.

For a production server, I prefer using a version manager such as NVM when I want easy control over Node versions.

Install NVM:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash

After installation, reload the shell:

source ~/.bashrc

Check NVM:

nvm --version

Then install the Node.js version required by the project.

For example:

nvm install --lts

Use the project’s required Node version if the project specifies one.

Check:

node -v

and:

npm -v

Step 5: Clone the Astro Project

Now I need to put my Astro project on the VPS.

I can clone it from GitHub.

For example:

cd /var/www

Create a directory for the website:

sudo mkdir -p /var/www/my-astro-site

Give the directory to my deployment user if necessary, then clone the repository.

A common approach is:

cd /var/www
git clone YOUR_GITHUB_REPOSITORY_URL my-astro-site

Then:

cd /var/www/my-astro-site

I can check the project files:

ls

I should see files such as:

package.json
astro.config.mjs
src
public

depending on the project.


Step 6: Install Dependencies

Inside the project directory:

npm install

If the project uses a lockfile, I prefer using:

npm ci

for a reproducible production installation.

The exact command depends on the package manager and lockfile used by the project.

For example:

package-lock.json  -> npm ci
pnpm-lock.yaml     -> pnpm install --frozen-lockfile
yarn.lock          -> yarn install --frozen-lockfile

Step 7: Configure Astro for the Server

This step depends on how my Astro website is built.

If the project is a static website, I can generate static files.

If the project uses Astro SSR, I need an appropriate Astro adapter.

For a Node.js server deployment, the project commonly uses:

npm install @astrojs/node

Then my Astro configuration can use the Node adapter.

For example:

import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
    output: 'server',
    adapter: node({
        mode: 'standalone'
    })
});

The exact configuration depends on the Astro version and project requirements.

The important thing to understand is that:

Static Astro
     |
     v
HTML/CSS/JS files

SSR Astro
     |
     v
Node.js application

I should decide which deployment model my project actually needs before setting up the server.


Step 8: Build the Astro Project

Once dependencies and configuration are ready:

npm run build

Astro will build the production version of the application.

If everything is configured correctly, the command should complete successfully.

I recommend fixing all build errors before moving to Nginx or PM2.

If the project doesn’t build locally, deploying it to the VPS won’t magically fix it.


Step 9: Test the Astro Application

Before configuring Nginx, I like to test the application directly.

For an Astro Node deployment, the exact command depends on the generated output and package scripts.

A common setup is:

npm run start

or:

node ./dist/server/entry.mjs

If Astro is listening on port 4321, I can test it locally on the VPS:

curl http://127.0.0.1:4321

If I receive HTML output, the application is running.

At this stage:

Browser
   X
   |
   v
Nginx not configured yet

VPS
   |
   v
Astro
   |
   v
Port 4321

Once this works, I can move to the reverse proxy configuration.


Step 10: Install PM2

Now I want the Astro Node process to keep running.

I install PM2:

npm install -g pm2

Check it:

pm2 --version

Step 11: Start Astro With PM2

I can start the Astro server using PM2.

For example:

pm2 start ./dist/server/entry.mjs --name my-astro-site

If the application needs a specific host and port, I can configure environment variables.

For example:

HOST=127.0.0.1 PORT=4321 pm2 start ./dist/server/entry.mjs --name my-astro-site

Then check:

pm2 list

I should see something similar to:

┌────┬────────────────┬─────────┬─────────┐
│ id │ name           │ status  │ uptime  │
├────┼────────────────┼─────────┼─────────┤
│ 0  │ my-astro-site  │ online  │ ...     │
└────┴────────────────┴─────────┴─────────┘

Step 12: Save the PM2 Process

PM2 needs to know which applications should be restored after a server reboot.

Run:

pm2 save

Then generate the startup command:

pm2 startup

PM2 will give me a command.

I need to copy and execute the command it provides.

Then run:

pm2 save

Now PM2 can restore the application after the server restarts.


Step 13: Install Nginx

Now I install Nginx:

sudo apt install nginx -y

Check its status:

sudo systemctl status nginx

If everything is working, I should see that Nginx is active.

I can also test:

http://YOUR_SERVER_IP

The default Nginx page should appear.


Step 14: Point the Domain to the VPS

Before configuring Nginx for my domain, I need to point the domain to the VPS.

Suppose my domain is:

example.com

In my domain DNS settings, I create an A record:

Type: A
Name: @
Value: YOUR_SERVER_IP

For:

www.example.com

I can create another record:

Type: A
Name: www
Value: YOUR_SERVER_IP

DNS changes can take some time to propagate.

I can check the domain using:

ping example.com

or a DNS lookup tool.


Step 15: Create an Nginx Configuration

Now I need to tell Nginx what to do when someone visits my domain.

Create a configuration file:

sudo nano /etc/nginx/sites-available/my-astro-site

Add:

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

    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:4321;

        proxy_http_version 1.1;

        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;
    }
}

Replace:

example.com

with your actual domain.

The important line is:

proxy_pass http://127.0.0.1:4321;

This tells Nginx:

Forward requests to my Astro application running on port 4321.


Step 16: Enable the Nginx Configuration

Now I create a symbolic link:

sudo ln -s /etc/nginx/sites-available/my-astro-site /etc/nginx/sites-enabled/

Before restarting Nginx, I always test the configuration.

sudo nginx -t

If everything is correct, I should see:

syntax is ok
test is successful

Then reload Nginx:

sudo systemctl reload nginx

Now I can open:

http://example.com

If DNS is configured correctly and Astro is running, the website should appear.


Step 17: Add HTTPS With Let’s Encrypt

I don’t want visitors using plain HTTP in production.

I want:

https://example.com

instead of:

http://example.com

A common way to configure free SSL certificates on Ubuntu with Nginx is Certbot.

Install Certbot and its Nginx plugin:

sudo apt install certbot python3-certbot-nginx -y

Then run:

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

Certbot can obtain the certificate and update the Nginx configuration.

After that, open:

https://example.com

Your Astro website should now be accessible over HTTPS.


Step 18: Check Automatic Certificate Renewal

Let’s Encrypt certificates are short-lived, so I don’t want to manually renew them every time.

Certbot generally installs automatic renewal functionality.

I can test the renewal process:

sudo certbot renew --dry-run

If the test succeeds, automatic renewal should be configured correctly.


Step 19: Configure a Firewall

A VPS should not expose every possible port to the internet.

If I use Ubuntu’s UFW firewall, I can allow SSH:

sudo ufw allow OpenSSH

Allow HTTP:

sudo ufw allow 80/tcp

Allow HTTPS:

sudo ufw allow 443/tcp

Then enable UFW:

sudo ufw enable

Check the rules:

sudo ufw status

One important thing I remember is that I generally do not need to expose the Astro port publicly.

For example, if Astro runs on:

127.0.0.1:4321

Nginx can access it internally.

Visitors only need access to:

80
443

and SSH on the port I use.


Step 20: Useful PM2 Commands

Once the website is deployed, I frequently use PM2 commands.

See running applications

pm2 list

View logs

pm2 logs my-astro-site

Restart application

pm2 restart my-astro-site

Stop application

pm2 stop my-astro-site

Remove application from PM2

pm2 delete my-astro-site

View application details

pm2 show my-astro-site

These commands are worth remembering.


Updating the Astro Website

Now suppose I make changes to my Astro project and push them to GitHub.

On the VPS:

cd /var/www/my-astro-site

Pull the latest code:

git pull

Install dependencies if required:

npm ci

Build the project:

npm run build

Then restart the application:

pm2 restart my-astro-site

The updated version should now be live.

So my basic deployment workflow becomes:

Local Changes
     |
     v
Git Commit
     |
     v
Git Push
     |
     v
VPS
     |
     v
git pull
     |
     v
npm ci
     |
     v
npm run build
     |
     v
PM2 restart
     |
     v
Website Updated

What If I Use GitHub Actions?

Manually running:

git pull
npm ci
npm run build
pm2 restart

every time works, but eventually I may want to automate it.

This is where GitHub Actions becomes useful.

I can create a deployment workflow that:

  1. Detects a push to the production branch
  2. Connects to the VPS through SSH
  3. Pulls the latest code
  4. Installs dependencies
  5. Builds the Astro project
  6. Restarts PM2

Then my deployment becomes:

git push
    |
    v
GitHub Actions
    |
    v
VPS
    |
    v
Build
    |
    v
PM2 Restart

This is one of the first steps I would take when moving from manual deployment toward a proper CI/CD workflow.


Static Astro vs SSR Astro on a VPS

Before deploying Astro, I always ask myself:

Does this project actually need a Node server?

Astro can be used to generate static websites.

For example:

Blog
Portfolio
Documentation
Landing Page
Company Website

may be suitable for static output.

In that situation, the architecture can be much simpler:

User
  |
  v
Nginx
  |
  v
Static HTML/CSS/JS

I don’t necessarily need PM2 or a Node server for every static Astro website.

For SSR:

User
  |
  v
Nginx
  |
  v
PM2
  |
  v
Node.js
  |
  v
Astro SSR

So I should not automatically add PM2 just because the project uses Astro.

The deployment method should match the project’s output mode.


Common Problems and Their Solutions

1. Nginx Shows 502 Bad Gateway

If I see:

502 Bad Gateway

I first check whether Astro is running.

pm2 list

Then:

pm2 logs my-astro-site

I also check the port:

curl http://127.0.0.1:4321

If this doesn’t work, the problem is probably with the Astro application rather than Nginx.


2. Website Works on Port 4321 but Not Through Domain

If:

http://127.0.0.1:4321

works but:

https://example.com

doesn’t, I check:

DNS
Nginx configuration
Firewall
SSL configuration

I also run:

sudo nginx -t

3. Astro Works Locally but Not on VPS

I check the Node.js version:

node -v

Then compare it with the version required by the project.

I also run:

npm run build

directly on the VPS.

Build errors usually tell me what needs to be fixed.


4. Changes Are Not Appearing

I check:

git status

Then:

git pull

Build again:

npm run build

And restart:

pm2 restart my-astro-site

If the browser still shows old content, I also check browser caching and any caching layer in front of the server.


5. PM2 Application Keeps Restarting

I check:

pm2 logs my-astro-site

If the application crashes immediately, PM2 itself isn’t necessarily the problem.

There may be an application error, missing environment variable, incorrect Node version, or incorrect start command.


Where Should I Store Multiple Websites?

If I host multiple projects on the same VPS, I like keeping them organized.

For example:

/var/www/
    website-one/
    website-two/
    website-three/

Each application can have its own:

Git repository
Node application
PM2 process
Nginx configuration
Domain
SSL certificate

For example:

example.com
      |
      v
Nginx
      |
      v
127.0.0.1:4321
      |
      v
Astro Website 1


blog.example.com
      |
      v
Nginx
      |
      v
127.0.0.1:4322
      |
      v
Astro Website 2

This allows one VPS to host multiple applications.


The Complete Architecture

After everything is configured, the production setup looks like this:

                    INTERNET
                        |
                        v
                  example.com
                        |
                        v
                     NGINX
                  /           \
                 /             \
              HTTP             HTTPS
                                |
                                v
                        Reverse Proxy
                                |
                                v
                     127.0.0.1:4321
                                |
                                v
                             PM2
                                |
                                v
                            Node.js
                                |
                                v
                          Astro SSR

This might look complicated when written as a diagram, but each component has a specific responsibility.

Domain       -> Points visitors to the server

Nginx        -> Receives web requests

SSL          -> Provides HTTPS

PM2          -> Keeps Node running

Node.js      -> Runs the Astro server

Astro        -> Generates and serves the application

My Basic Astro VPS Deployment Checklist

When I deploy an Astro project to a new VPS, I generally work through this checklist:

[ ] Create VPS
[ ] Install updates
[ ] Install Git
[ ] Install Node.js
[ ] Clone repository
[ ] Install dependencies
[ ] Configure Astro
[ ] Build project
[ ] Test Astro locally
[ ] Install PM2
[ ] Start Astro with PM2
[ ] Save PM2 process
[ ] Install Nginx
[ ] Point domain to VPS
[ ] Configure Nginx
[ ] Test Nginx
[ ] Configure HTTPS
[ ] Test SSL renewal
[ ] Configure firewall
[ ] Test website

Once I have done this a few times, deploying another Astro application becomes much faster.


Final Thoughts

Hosting an Astro website on a VPS may seem difficult when I look at all the commands involved.

But when I break it down, there are only a few important pieces.

Astro is my application.

Node.js runs the server-side Astro application when I use SSR.

PM2 keeps the Node process running.

Nginx receives requests from visitors and forwards them to Astro.

DNS connects my domain to the VPS.

Let’s Encrypt provides HTTPS.

And Git gives me a convenient way to get my project onto the server and update it.

The most important thing I learned is that I don’t need to memorize every command.

I need to understand how the pieces connect:

Domain
   ↓
Nginx
   ↓
PM2
   ↓
Node.js
   ↓
Astro

Once I understand that architecture, troubleshooting becomes much easier.

If the domain doesn’t work, I check DNS.

If Nginx returns 502, I check the Astro process.

If the application crashes, I check PM2 logs.

If the build fails, I check the Node version, dependencies, environment variables, and Astro configuration.

That is what makes VPS deployment much less intimidating.

And once I am comfortable with this setup, the next natural step is automating the deployment with GitHub Actions and CI/CD, so I don’t have to manually log into the VPS every time I publish a new version.

Different Types of APIs: A Complete Guide for BeginnersArticlesCSS Flexbox and CSS Grid Complete Guide for Beginners

Oops 😥 I haven't written more articles related to Astro.