This guide explains how to deploy a Next.js app on a VPS running Ubuntu Server, using PM2, Nginx, and GitHub Actions for continuous deployment. SSL is handled by Certbot, and Fail2Ban with Nginx rate limiting is used for basic security.
- Get a VPS: Purchase a VPS running Ubuntu Server from your preferred cloud provider.
- Connect via SSH: Open a terminal and connect to your VPS.
ssh username@your-vps-ip
- Install NVM (Node Version Manager):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash source ~/.bashrc nvm install --lts
- Verify installation:
node -v
- Install PM2:
npm install pm2@latest -g
- Verify PM2 installation:
pm2 -v
- Create a directory for your app:
mkdir ~/my-app cd ~/my-app
- Clone your repository:
git clone https://github.com/your-username/your-repo.git .
- Install your application dependencies:
npm install
- Start your Next.js app using PM2:
pm2 start npm --name "my-app" -- start
- Save the PM2 process:
pm2 save
- Configure PM2 to start on server boot:
pm2 startup
- Install Nginx:
sudo apt update sudo apt install nginx
- Configure Nginx to point to your Node.js app:
sudo nano /etc/nginx/sites-available/my-app
- Add the following Nginx configuration:
server { listen 80; server_name my-app.example.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
- Enable the configuration:
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
- Test and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
- Go to your DNS/domain provider and set up an A record pointing
my-app.example.com
to your VPS's public IP.
- Install Certbot:
sudo apt install certbot python3-certbot-nginx
- Generate SSL certificate:
sudo certbot --nginx -d my-app.example.com
- Enable auto-renewal:
sudo systemctl enable certbot.timer
- Install Fail2Ban:
sudo apt install fail2ban
- Configure Nginx rate limiting by adding these lines to
/etc/nginx/nginx.conf
:limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
- Apply rate limiting to your app:
location / { limit_req zone=one burst=5; proxy_pass http://localhost:3000; }
- Restart Nginx:
sudo systemctl restart nginx
- Create a
deploy.sh
script for building and restarting your app.#!/bin/bash cd /path/to/your/app git pull origin main npm run build rm -rf .next && mv .next_temp .next pm2 restart my-app
- Make the script executable:
chmod +x deploy.sh
- Create a simple Node.js webhook listener:
const express = require('express'); const { exec } = require('child_process'); const app = express(); app.use(express.json()); const SECRET = 'your-secret-token'; app.post('/webhook', (req, res) => { const token = req.headers['x-secret']; if (token === SECRET) { exec('./deploy.sh', (err, stdout, stderr) => { if (err) return res.status(500).send('Deployment failed.'); res.status(200).send('Deployment successful.'); }); } else { res.status(401).send('Unauthorized'); } }); app.listen(4000, () => console.log('Webhook listener running on port 4000'));
- Start the app with PM2:
pm2 start node webhook.js --name "webhook"
- Add a GitHub Actions workflow under
.github/workflows/deploy.yml
:name: Deploy to VPS on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Trigger deployment webhook run: | curl -X POST https://my-app.example.com/webhook -H "x-secret: your-secret-token"