Building your web app with Next.js gives you the power of server-side rendering, hybrid static + dynamic rendering, and React’s ecosystem. But deploying an SSR Next.js app isn’t quite the same as deploying a plain frontend SPA. You need a running Node.js server, proper reverse proxying, process management, SSL, and deployment scripts.
In this guide, I’ll walk you through step by step how to deploy a Next.js SSR site on a Linux (Ubuntu, Debian, or similar) environment. By the end, you’ll have a production-ready SSR deployment, with automated restarts and secure HTTPS.
1. Prerequisites & System Setup
Before deploying, make sure you have:
- A Next.js project configured and tested locally
- A Linux server (VPS or cloud instance) with SSH access
- A domain name pointing (A/AAAA) to that server
- Basic familiarity with shell, SSH, and Linux commands
On your local development side, in package.json, include scripts:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Ensure your Next.js version and dependencies work in production mode.
If using features like image optimization, ISR, or middleware, make sure they’re compatible under self-hosting.
2. Prepare Your Next.js App for Production
On your local machine (or in CI), do the following:
Build the application:
npm run build
This will compile pages, optimize assets, etc.
(Optional) Test production start:
npm run start
Confirm the server starts and SSR pages render correctly.
If your application has environment variables, ensure you manage both build-time and runtime variables appropriately. Use .env.production or a secrets store on the server, and prefix any public variables with NEXT_PUBLIC_.
(Optional optimization) Use next.config.js or custom configurations (like enabling sharp for image processing) for production performance.
Once things run locally, you're ready to deploy.
3. Provision Your Linux Server
Log into your server via SSH and run:
sudo apt update
sudo apt upgrade -y
Then install the necessary base dependencies:
sudo apt install -y git curl build-essential
Install Node.js (recommended version: the LTS you used locally). You can use NodeSource, nvm, or your preferred method. For example:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
Check node -v and npm -v.
You should also install nginx:
sudo apt install -y nginx
Make sure the firewall allows HTTP (80) and HTTPS (443):
sudo ufw allow 'Nginx Full'
sudo ufw enable
4. Deploy the App (Clone, Build, Start)
On the server:
Choose a directory, e.g. /var/www/myapp or /home/username/apps/myapp.
Clone your repository (or pull from remote):
cd /var/www
sudo mkdir myapp
sudo chown $USER:$USER myapp
cd myapp
git clone <your-repo-url> .
Install dependencies:
npm install
Build:
npm run build
Start the server (we’ll wrap this in a process manager soon). For testing:
npm run start
By default, Next.js listens on port 3000.
If this works, you’re ready to set up process management and reverse proxy.
5. Process Management: PM2 or systemd
You don’t want your app to crash and disappear. Use a manager:
Option A: PM2
Install it globally:
sudo npm install -g pm2
Start your app under PM2:
pm2 start npm --name "myapp" -- start
You can scale or restart easily. Then save the PM2 process list and enable startup:
pm2 save
pm2 startup
This ensures PM2 (and your app) restarts automatically on server reboot.
Option B: systemd service
Create a systemd unit file /etc/systemd/system/myapp.service:
[Unit]
Description=Next.js App – myapp
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/npm run start
Restart=on-failure
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
Check status:
sudo systemctl status myapp
Either approach works well.
6. Reverse Proxy with NGINX
We’ll configure NGINX to handle incoming HTTP/HTTPS and forward traffic to your Next.js server on localhost:3000.
Create a new site config, e.g. /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name example.com www.example.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 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Enable it:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Now HTTP requests to your domain will be forwarded to your Next.js app.
7. SSL/TLS Setup with Certbot
To secure it with HTTPS:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot will automatically modify your NGINX config, obtain certificates, and set up a renewal job. After success, your site will be HTTPS-enabled.
You should test renewal:
sudo certbot renew --dry-run
8. Zero-Downtime & Deployment Automation
Manual deployment is fine for small sites, but for production you want safer deployment:
- Use a staging branch or CI (GitHub Actions, GitLab CI) to build and test.
- Transfer the built app (or container) to server.
- Use “blue/green” or rolling strategies: deploy new version to a separate directory and switch symlink.
- Graceful restarts: ensure the old process stays alive until new one is ready.
- PM2 offers reload commands (e.g. pm2 reload) that help with zero-downtime restarts.
- Backups of application and environment variables are essential.
A sample script:
# On server
cd /var/www
git pull origin main
npm install
npm run build
pm2 reload myapp
You can wrap this in a shell script and execute via SSH from CI.
9. Monitoring, Logs & Health Checks
- Use PM2 or journalctl to view logs and errors.
- Monitor memory, CPU, response times.
- Use uptime checks (e.g. ping /health endpoint).
- Set up alerting (e.g. email or Slack) for crashes or high resource usage.
- Enable log rotation to avoid disk overflows.
10. Tips, Caveats & Scaling Considerations
- Use NODE_ENV=production for optimal behavior.
- Avoid building on server in high-load environments; prefer building in CI and deploy artifacts.
- For SSR with large loads, consider using multiple instances behind a load balancer.
- Shared cache (for ISR or image cache) may require storing cache in a shared store (Redis, S3) rather than local disk.
- Keep dependencies (e.g. sharp) updated and compatible on Linux.
- If your app uses WebSockets or serverless features (Edge functions), ensure your architecture supports them.
- Regularly renew SSL, update system packages, and monitor for security patches.
Conclusion
Deploying a Next.js SSR site on Linux is fully achievable with a few core components:
- A Node.js server running your Next.js app
- A process manager (PM2 or systemd)
- NGINX as a reverse proxy
- SSL via Certbot
- Deployment scripts or CI automation
Once set up, you’ll have a reliable, maintainable SSR deployment under your control.