Deploy Astro to VPS: Complete Guide 2026
Master the complete process of deploying Astro applications to a VPS with server-side rendering, process management, and production-grade security. This guide covers everything from choosing between SSR and static builds to automating deployments with DeployWise.
Table of Contents
Astro SSR vs Static: Choosing Your Deployment Model
Before deploying Astro to a VPS, you need to decide between two deployment models:
Static (Default)
- +Pre-rendered HTML, maximum performance
- +No server needed, works on any static host
- +Excellent SEO with pre-rendered pages
- -No dynamic content or user sessions
- -Rebuild required for content updates
SSR (Server-Side Rendering)
- +Dynamic content and user sessions
- +Real-time data and database queries
- +No rebuild needed for content changes
- -Requires Node.js server (VPS needed)
- -Higher server resource requirements
For this guide, we'll focus on SSR deployment since it requires a VPS. Static sites can be deployed to Vercel, Netlify, or any CDN with a single command.
Setting Up @astrojs/node Adapter
The @astrojs/node adapter enables SSR for Astro applications on Node.js environments. Install and configure it for your VPS deployment.
Step 1: Install the Adapter
npm install @astrojs/node
Step 2: Update astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone',
}),
});The mode: 'standalone' option creates a fully self-contained server that can run independently without needing Astro's dev server.
Key Adapter Options
- mode:
'standalone'or'middleware'. Use standalone for VPS. - start: Custom start command (optional)
- host: Listening host, default
localhost - port: Server port, default
3000
Build Configuration and Output Settings
Understanding the difference between output: 'static' andoutput: 'server' is crucial for VPS deployment.
Static vs Server Output
output: 'static'
- • Generates
dist/with HTML files - • No Node.js runtime needed
- • Best for static sites and CDNs
output: 'server'
- • Generates
dist/server/for Node.js - • Includes client assets in
dist/client/ - • Requires Node.js runtime on VPS
- • Enables dynamic rendering and SSR
Complete astro.config.mjs for VPS
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone',
}),
vite: {
ssr: {
external: ['sharp'],
},
},
});Building for Production
npm run build
This generates dist/ with:
- •
dist/server/- Node.js entry point - •
dist/client/- Static assets (CSS, JS, images) - •
package.json- Dependencies in dist root
PM2 Process Management Configuration
PM2 keeps your Astro application running, handles automatic restarts, and manages multiple processes. It's production-ready process management for Node.js applications.
Step 1: Install PM2 Globally
npm install -g pm2
Step 2: Create PM2 Configuration (ecosystem.config.js)
module.exports = {
apps: [
{
name: 'astro-app',
script: './dist/server/entry.mjs',
exec_mode: 'cluster',
instances: 'max',
autorestart: true,
watch: false,
env: {
NODE_ENV: 'production',
HOST: '127.0.0.1',
PORT: 3000,
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time_format: 'YYYY-MM-DD HH:mm:ss Z',
max_memory_restart: '500M',
max_restarts: 10,
min_uptime: '10s',
},
],
};Step 3: Start the Application
# Start the application pm2 start ecosystem.config.js # Save PM2 configuration pm2 save # Enable startup on system boot pm2 startup pm2 save
Useful PM2 Commands
- pm2 list - Show all running processes
- pm2 logs astro-app - View application logs
- pm2 restart astro-app - Restart the application
- pm2 stop astro-app - Stop the application
- pm2 delete astro-app - Remove from PM2
- pm2 monit - Real-time monitoring dashboard
Nginx Reverse Proxy Setup
Nginx acts as a reverse proxy, forwarding requests from port 80/443 to your Node.js application running on port 3000. This setup provides better performance and security.
Step 1: Install Nginx
# Ubuntu/Debian sudo apt update sudo apt install nginx # Start Nginx sudo systemctl start nginx sudo systemctl enable nginx
Step 2: Create Nginx Configuration
Create /etc/nginx/sites-available/astro.conf:
upstream astro_app {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL certificates (configure after Let's Encrypt setup)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css text/javascript application/json application/javascript;
gzip_min_length 1000;
# Client upload size
client_max_body_size 50M;
location / {
proxy_pass http://astro_app;
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;
proxy_read_timeout 60s;
proxy_connect_timeout 60s;
}
# Static asset caching
location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}Step 3: Enable the Configuration
# Create symlink sudo ln -s /etc/nginx/sites-available/astro.conf /etc/nginx/sites-enabled/ # Test Nginx configuration sudo nginx -t # Reload Nginx sudo systemctl reload nginx
SSL Certificates with Let's Encrypt
Certbot automates the installation and renewal of free SSL certificates from Let's Encrypt, ensuring your application is always served over HTTPS.
Step 1: Install Certbot
# Ubuntu/Debian sudo apt update sudo apt install certbot python3-certbot-nginx # Verify installation certbot --version
Step 2: Obtain and Install Certificate
# Certbot will automatically update your Nginx config sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Step 3: Enable Auto-Renewal
# Check renewal configuration sudo certbot renew --dry-run # Enable auto-renewal timer sudo systemctl enable certbot.timer sudo systemctl start certbot.timer # Verify timer is running sudo systemctl status certbot.timer
Let's Encrypt certificates are valid for 90 days and auto-renew 30 days before expiration.
Common Mistakes and How to Avoid Them
Mistake 1: Forgetting output: 'server' Configuration
The most common error is deploying with output: 'static' (the default) instead of output: 'server'.
// ❌ WRONG - This won't work with @astrojs/node
export default defineConfig({
// output: 'static' by default
adapter: node(),
});
// ✅ CORRECT
export default defineConfig({
output: 'server',
adapter: node(),
});Mistake 2: Looking for dist/ Instead of dist/server/
SSR builds output to dist/server/entry.mjs, not the rootdist/ directory.
# ❌ WRONG - This file doesn't exist with SSR node dist/entry.mjs # ✅ CORRECT - SSR entry point node dist/server/entry.mjs # Or use PM2 with ecosystem.config.js pm2 start ecosystem.config.js
Mistake 3: Not Installing Dependencies on VPS
The build directory needs node_modules on the VPS.
# Deploy process: # 1. Build locally or in CI/CD npm run build # 2. Upload dist/ folder to VPS # 3. Install dependencies on VPS cd /var/www/astro-app npm install --production # 4. Start with PM2 pm2 start ecosystem.config.js
Mistake 4: Incorrect Nginx Upstream Configuration
Make sure Nginx connects to the correct port where your Node.js app is running.
# Check which port your app is using
pm2 logs astro-app | grep "listening on"
# Update Nginx upstream to match
upstream astro_app {
server 127.0.0.1:3000; # Change 3000 to your actual port
keepalive 32;
}Mistake 5: Missing Environment Variables
Astro applications often need environment variables (database URLs, API keys, etc.).
# Create .env file on VPS
/var/www/astro-app/.env
DATABASE_URL=postgres://...
API_KEY=your-api-key
# Update ecosystem.config.js to include env variables
{
apps: [{
name: 'astro-app',
script: './dist/server/entry.mjs',
env: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: process.env.DATABASE_URL,
API_KEY: process.env.API_KEY,
},
}],
}Mistake 6: Adapter Mode Confusion
The mode setting changes how the adapter works.
// mode: 'standalone' (recommended for VPS)
// Creates a self-contained server, run with: node dist/server/entry.mjs
adapter: node({ mode: 'standalone' })
// mode: 'middleware'
// Requires Express.js or other framework integration
adapter: node({ mode: 'middleware' })
// For VPS deployment, always use 'standalone'Automating with DeployWise
DeployWise automates the entire Astro deployment process, eliminating manual configuration errors and reducing deployment time from hours to minutes.
What DeployWise Handles Automatically
Quick Start with DeployWise
# 1. Connect your VPS to DeployWise # 2. Create a new project and select your Git repository # 3. DeployWise detects your Astro project # 4. Configure deployment settings (domain, environment variables) # 5. Deploy with a single click # That's it! DeployWise handles: # - Building your Astro project # - Uploading to VPS # - Installing dependencies # - Configuring PM2 # - Setting up Nginx # - Installing SSL certificates # - Monitoring the application
DeployWise Benefits for Astro
- •Zero configuration - handles Astro-specific settings automatically
- •One-click deployments with rollback capability
- •Real-time monitoring and logs
- •Automatic SSL renewal and security updates
- •Git integration with automatic deployments on push
Ready to Deploy Your Astro App?
Skip the manual configuration and deploy your Astro application to VPS in minutes with DeployWise.
Get Started with DeployWiseQuick Reference: Deployment Checklist
- Install @astrojs/node adapter
- Set output: 'server' in astro.config.mjs
- Build project with npm run build
- Create ecosystem.config.js for PM2
- Configure Nginx reverse proxy
- Install SSL certificate with Certbot
- Start application with PM2
- Monitor logs and performance