$ ssh root@your-server-ip
ssh root@your-server-ip
ssh root@your-server-ip
-weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y
-weight: 500;">apt -weight: 500;">install -y -weight: 500;">curl -weight: 500;">git ufw
-weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y
-weight: 500;">apt -weight: 500;">install -y -weight: 500;">curl -weight: 500;">git ufw
-weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y
-weight: 500;">apt -weight: 500;">install -y -weight: 500;">curl -weight: 500;">git ufw
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw -weight: 500;">enable
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw -weight: 500;">enable
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw -weight: 500;">enable
adduser deploy
usermod -aG -weight: 600;">sudo deploy
adduser deploy
usermod -aG -weight: 600;">sudo deploy
adduser deploy
usermod -aG -weight: 600;">sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
-weight: 500;">curl -fsSL https://deb.nodesource.com/setup_20.x | -weight: 600;">sudo -E bash -
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y nodejs
-weight: 500;">curl -fsSL https://deb.nodesource.com/setup_20.x | -weight: 600;">sudo -E bash -
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y nodejs
-weight: 500;">curl -fsSL https://deb.nodesource.com/setup_20.x | -weight: 600;">sudo -E bash -
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y nodejs
node --version # Should be 20.x
-weight: 500;">npm --version
node --version # Should be 20.x
-weight: 500;">npm --version
node --version # Should be 20.x
-weight: 500;">npm --version
cd /home/deploy
-weight: 500;">git clone https://github.com/your-username/your-nextjs-app.-weight: 500;">git
cd your-nextjs-app
-weight: 500;">npm ci
cd /home/deploy
-weight: 500;">git clone https://github.com/your-username/your-nextjs-app.-weight: 500;">git
cd your-nextjs-app
-weight: 500;">npm ci
cd /home/deploy
-weight: 500;">git clone https://github.com/your-username/your-nextjs-app.-weight: 500;">git
cd your-nextjs-app
-weight: 500;">npm ci
cp .env.example .env.production
nano .env.production
# Fill in your production values: DATABASE_URL, API keys, etc.
cp .env.example .env.production
nano .env.production
# Fill in your production values: DATABASE_URL, API keys, etc.
cp .env.example .env.production
nano .env.production
# Fill in your production values: DATABASE_URL, API keys, etc.
-weight: 500;">npm run build
-weight: 500;">npm run build
-weight: 500;">npm run build
-weight: 600;">sudo -weight: 500;">npm -weight: 500;">install -g pm2
-weight: 600;">sudo -weight: 500;">npm -weight: 500;">install -g pm2
-weight: 600;">sudo -weight: 500;">npm -weight: 500;">install -g pm2
pm2 -weight: 500;">start -weight: 500;">npm --name "nextjs-app" -- -weight: 500;">start
pm2 -weight: 500;">start -weight: 500;">npm --name "nextjs-app" -- -weight: 500;">start
pm2 -weight: 500;">start -weight: 500;">npm --name "nextjs-app" -- -weight: 500;">start
pm2 -weight: 500;">status
pm2 logs nextjs-app
pm2 -weight: 500;">status
pm2 logs nextjs-app
pm2 -weight: 500;">status
pm2 logs nextjs-app
-weight: 500;">curl http://localhost:3000
-weight: 500;">curl http://localhost:3000
-weight: 500;">curl http://localhost:3000
pm2 startup
pm2 save
pm2 startup
pm2 save
pm2 startup
pm2 save
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y nginx
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y nginx
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y nginx
-weight: 600;">sudo nano /etc/nginx/sites-available/your-app
-weight: 600;">sudo nano /etc/nginx/sites-available/your-app
-weight: 600;">sudo nano /etc/nginx/sites-available/your-app
server { listen 80; server_name yourdomain.com www.yourdomain.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection '-weight: 500;">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; }
}
server { listen 80; server_name yourdomain.com www.yourdomain.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection '-weight: 500;">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; }
}
server { listen 80; server_name yourdomain.com www.yourdomain.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection '-weight: 500;">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; }
}
-weight: 600;">sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
-weight: 600;">sudo nginx -t
-weight: 600;">sudo -weight: 500;">systemctl reload nginx
-weight: 600;">sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
-weight: 600;">sudo nginx -t
-weight: 600;">sudo -weight: 500;">systemctl reload nginx
-weight: 600;">sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
-weight: 600;">sudo nginx -t
-weight: 600;">sudo -weight: 500;">systemctl reload nginx
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y certbot python3-certbot-nginx
-weight: 600;">sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y certbot python3-certbot-nginx
-weight: 600;">sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
-weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y certbot python3-certbot-nginx
-weight: 600;">sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
-weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status certbot.timer
-weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status certbot.timer
-weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status certbot.timer
#!/bin/bash
set -e cd /home/deploy/your-nextjs-app echo "Pulling latest code..."
-weight: 500;">git pull origin main echo "Installing dependencies..."
-weight: 500;">npm ci echo "Building..."
-weight: 500;">npm run build echo "Restarting..."
pm2 -weight: 500;">restart nextjs-app echo "Done."
#!/bin/bash
set -e cd /home/deploy/your-nextjs-app echo "Pulling latest code..."
-weight: 500;">git pull origin main echo "Installing dependencies..."
-weight: 500;">npm ci echo "Building..."
-weight: 500;">npm run build echo "Restarting..."
pm2 -weight: 500;">restart nextjs-app echo "Done."
#!/bin/bash
set -e cd /home/deploy/your-nextjs-app echo "Pulling latest code..."
-weight: 500;">git pull origin main echo "Installing dependencies..."
-weight: 500;">npm ci echo "Building..."
-weight: 500;">npm run build echo "Restarting..."
pm2 -weight: 500;">restart nextjs-app echo "Done."
chmod +x /home/deploy/deploy.sh
chmod +x /home/deploy/deploy.sh
chmod +x /home/deploy/deploy.sh
# .github/workflows/deploy.yml
name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy to VPS uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_IP }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: /home/deploy/deploy.sh
# .github/workflows/deploy.yml
name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy to VPS uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_IP }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: /home/deploy/deploy.sh
# .github/workflows/deploy.yml
name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy to VPS uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_IP }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: /home/deploy/deploy.sh
pm2 -weight: 500;">install pm2-logrotate
pm2 -weight: 500;">install pm2-logrotate
pm2 -weight: 500;">install pm2-logrotate - A VPS from any provider (Hetzner, DigitalOcean, Linode, Vultr, whatever). 2GB RAM minimum for a Next.js app with a build step. 4GB if you're running a database on the same box.
- A domain name pointed at your server's IP address.
- SSH access to the server.
- A Next.js app that builds successfully with -weight: 500;">npm run build on your local machine. - No health check. If the new build is broken, PM2 restarts the old process, but there's a window where requests fail.
- No rollback. If the deploy breaks the app, you have to manually revert.
- No preview environments. Every push to main goes straight to production.
- Downtime during -weight: 500;">restart. PM2's -weight: 500;">restart kills the old process and starts the new one. There's a 1-3 second gap with 502 errors. - Node.js (runtime)
- PM2 (process manager)
- Nginx (reverse proxy, SSL termination)
- Certbot (certificate renewal)
- Git (code delivery)
- GitHub Actions (build automation) - Uptime monitoring (external -weight: 500;">service)
- Error tracking (external -weight: 500;">service)
- Analytics (external -weight: 500;">service)
- Log management (PM2 + logrotate, or external -weight: 500;">service) - Vercel is the obvious choice for Next.js. They built the framework. Free tier is generous. Costs scale fast with traffic.
- Coolify is open-source, self-hosted. Handles Docker, Traefik proxy, SSL, and -weight: 500;">git-push deploys. Good community.
- Dokploy is another open-source option, simpler than Coolify, focused on minimal configuration.
- Kamal (from the Rails team) deploys Docker containers to any server over SSH. Minimal abstraction.
- Temps is what I build. Single Rust binary that handles deployments plus analytics, error tracking, uptime monitoring, and session replay. One tool instead of 10. Smaller community, dashboard isn't as polished as Vercel's. Open source and free to self-host.