Self-Hosted Deployment
Deploy your Next.js template on your own infrastructure. Full control over your hosting environment.
Why Self-Host?
✅ Full Control - Complete infrastructure control ✅ Data Privacy - Keep data on your servers ✅ Custom Configuration - Unlimited customization ✅ Cost Control - Potentially lower costs at scale ✅ No Vendor Lock-in - Use any hosting provider
Prerequisites
- Linux server (Ubuntu 20.04+ recommended)
- Node.js 18.17 or later
- Domain name
- Basic server administration knowledge
Deployment Methods
Method 1: Docker (Recommended)
Docker provides consistent, portable deployments.
Step 1: Create Dockerfile
# Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Set environment variables
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]Step 2: Update next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// ... your other config
}
module.exports = nextConfigStep 3: Create docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_SITE_URL=https://yourdomain.com
restart: unless-stoppedStep 4: Build and Run
# Build image
docker build -t my-nextjs-app .
# Run container
docker run -p 3000:3000 my-nextjs-app
# Or use docker-compose
docker-compose up -dMethod 2: PM2 (Node.js Process Manager)
PM2 keeps your app running and handles crashes.
Step 1: Install PM2
npm install -g pm2Step 2: Build Application
npm run buildStep 3: Create ecosystem.config.js
module.exports = {
apps: [{
name: 'nextjs-app',
script: 'npm',
args: 'start',
cwd: '/path/to/your/app',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
NEXT_PUBLIC_SITE_URL: 'https://yourdomain.com'
}
}]
}Step 4: Start with PM2
# Start app
pm2 start ecosystem.config.js
# Save PM2 config
pm2 save
# Set PM2 to start on boot
pm2 startupPM2 Commands:
# Status
pm2 status
# Logs
pm2 logs
# Restart
pm2 restart nextjs-app
# Stop
pm2 stop nextjs-app
# Delete
pm2 delete nextjs-appMethod 3: Systemd Service
Create a systemd service for automatic startup.
Step 1: Create Service File
sudo nano /etc/systemd/system/nextjs-app.service[Unit]
Description=Next.js Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/nextjs-app
ExecStart=/usr/bin/npm start
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.targetStep 2: Enable and Start
# Reload systemd
sudo systemctl daemon-reload
# Enable on boot
sudo systemctl enable nextjs-app
# Start service
sudo systemctl start nextjs-app
# Check status
sudo systemctl status nextjs-appNginx Configuration
Use Nginx as reverse proxy for better performance and SSL.
Step 1: Install Nginx
sudo apt update
sudo apt install nginxStep 2: Configure Nginx
Create config file:
sudo nano /etc/nginx/sites-available/nextjs-app# Basic configuration
server {
listen 80;
server_name yourdomain.com www.yourdomain.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;
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;
}
# Optimize static files
location /_next/static {
proxy_pass http://localhost:3000;
proxy_cache_valid 60m;
add_header Cache-Control "public, max-age=3600, immutable";
}
# Optimize images
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
proxy_pass http://localhost:3000;
proxy_cache_valid 60m;
add_header Cache-Control "public, max-age=3600";
}
}Step 3: Enable Site
# Create symlink
sudo ln -s /etc/nginx/sites-available/nextjs-app /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginxSSL/HTTPS with Let’s Encrypt
Step 1: Install Certbot
sudo apt install certbot python3-certbot-nginxStep 2: Obtain Certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comStep 3: Auto-renewal
Certbot automatically sets up renewal. Test it:
sudo certbot renew --dry-runUpdated Nginx Config (HTTPS):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
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;
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;
}
}Environment Variables
Method 1: .env File
# .env.production
NODE_ENV=production
PORT=3000
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
NEXT_PUBLIC_API_URL=https://api.yourdomain.comLoad with:
export $(cat .env.production | xargs)
npm startMethod 2: In Process Manager
For PM2 (in ecosystem.config.js):
env: {
NODE_ENV: 'production',
PORT: 3000
}For systemd (in service file):
Environment=NODE_ENV=production
Environment=PORT=3000Monitoring
Server Monitoring
Install monitoring tools:
# htop for system resources
sudo apt install htop
# Monitor in real-time
htopApplication Logs
PM2 logs:
pm2 logsSystemd logs:
sudo journalctl -u nextjs-app -fNginx logs:
# Access log
sudo tail -f /var/log/nginx/access.log
# Error log
sudo tail -f /var/log/nginx/error.logContinuous Deployment
Basic Git Deployment Script
deploy.sh:
#!/bin/bash
# Navigate to app directory
cd /var/www/nextjs-app
# Pull latest changes
git pull origin main
# Install dependencies
npm ci
# Build application
npm run build
# Restart application
pm2 restart nextjs-app
# Or restart systemd service
# sudo systemctl restart nextjs-app
echo "Deployment complete!"Make executable:
chmod +x deploy.shAutomated Deployment with GitHub Actions
.github/workflows/deploy.yml:
name: Deploy to Server
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/nextjs-app
git pull origin main
npm ci
npm run build
pm2 restart nextjs-appSecurity Best Practices
1. Firewall Configuration
# Enable UFW
sudo ufw enable
# Allow SSH
sudo ufw allow ssh
# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Check status
sudo ufw status2. Keep System Updated
# Update packages
sudo apt update && sudo apt upgrade -y
# Auto-updates (optional)
sudo apt install unattended-upgrades3. Limit SSH Access
Edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication noRestart SSH:
sudo systemctl restart ssh4. Use Environment Variables
Never commit secrets:
# .gitignore
.env*
!.env.exampleBackup Strategy
Database Backups
# Backup script
#!/bin/bash
BACKUP_DIR="/var/backups/nextjs-app"
DATE=$(date +%Y%m%d_%H%M%S)
# Create backup
tar -czf $BACKUP_DIR/backup_$DATE.tar.gz /var/www/nextjs-app
# Keep only last 7 days
find $BACKUP_DIR -name "backup_*.tar.gz" -mtime +7 -deleteAutomated Backups
Add to crontab:
crontab -e
# Daily backup at 2 AM
0 2 * * * /path/to/backup-script.shPerformance Optimization
Enable Caching
Nginx caching:
# Cache configuration
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nextjs_cache:10m max_size=1g inactive=60m;
location / {
proxy_cache nextjs_cache;
proxy_cache_valid 200 60m;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
}Enable Compression
# Gzip compression
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;Troubleshooting
Application Won’t Start
# Check Node version
node --version
# Check build
npm run build
# Check logs
pm2 logs
# or
sudo journalctl -u nextjs-app -n 50Port Already in Use
# Find process using port
sudo lsof -i :3000
# Kill process
kill -9 <PID>Nginx Issues
# Test configuration
sudo nginx -t
# Check error logs
sudo tail -f /var/log/nginx/error.log
# Restart Nginx
sudo systemctl restart nginxSSL Certificate Issues
# Renew manually
sudo certbot renew
# Check certificate status
sudo certbot certificatesScaling Considerations
Load Balancing
For multiple servers, use Nginx load balancing:
upstream nextjs_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
location / {
proxy_pass http://nextjs_backend;
}
}CDN Integration
Use CDN for static assets:
- Upload
public/and.next/static/to CDN - Update asset URLs in configuration
Cost Estimation
Basic VPS:
- 1GB RAM, 1 CPU: $5-10/month
- 2GB RAM, 2 CPU: $10-20/month
- 4GB RAM, 2 CPU: $20-40/month
Additional Costs:
- Domain: $10-15/year
- SSL: Free (Let’s Encrypt)
- Backups: $1-5/month
- Monitoring: Free-$10/month
Next Steps
✅ Deployed? Now:
- Set up monitoring
- Configure automated backups
- Enable SSL/HTTPS
- Set up firewall
- Configure continuous deployment
- Monitor logs regularly
Need help? Email support@capricorn.engineering
Resources: