Deploying Next.js on a domestic VPS in Malaysia or Singapore gives your users single-digit millisecond latency while cutting hosting bills compared to US cloud platforms. This guide covers production-ready standalone builds, Docker packaging, Nginx reverse proxying, and automated crash recovery.
Managed platforms charge hefty premiums for serverless function executions and bandwidth outside North America. A domestic VPS lets you run Next.js App Router, Server Components, and API routes with zero execution timeouts and predictable monthly costs.
Why Deploy Next.js on a Local VPS?
For developers targeting audiences in Malaysia and Southeast Asia, datacenter proximity makes an immediate difference.
**Latency reduction**: Routing requests to a Singapore or Kuala Lumpur server drops round-trip times to 5-15 ms compared to 180-240 ms for US-East serverless regions.
**No cold starts**: Unlike serverless platforms where idle lambdas take 1-3 seconds to boot, a Node.js process on a persistent VPS responds instantly to every request.
**Cost predictability**: Rented compute instances cost fixed amounts regardless of traffic spikes, bandwidth usage, or image optimization operations.
**Full Node.js runtime access**: Long-running background jobs, persistent WebSocket connections, and direct filesystem operations work without artificial platform constraints.
Next.js Output Configuration: Standalone Mode
Standard Next.js builds include unnecessary development dependencies and node_modules bloat. The standalone output feature creates a minimal production build containing only necessary files.
Update your next.config.js or next.config.mjs file:
javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// Optional: assetPrefix if using a CDN for static assets
// assetPrefix: process.env.NODE_ENV === 'production' ? 'https://cdn.example.com' : undefined,
};
module.exports = nextConfig;
```When you run `npm run build`, Next.js traces all import paths and outputs a self-contained server at `.next/standalone/server.js`. This reduces final image sizes from 1 GB down to roughly 120 MB.
Dockerfile for Next.js Standalone Build
Use a multi-stage Docker build to separate dependency installation from production runtime.
dockerfile
FROM node:20-alpine AS base
# Stage 1: Install dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Stage 2: Build source code
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN npm run build
# Stage 3: Production runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy static assets and standalone build output
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
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
```Key points in this Dockerfile:
Multi-stage caching prevents downloading dependencies on every build.
Static files from public/ and .next/static must be copied explicitly because standalone mode omits them.
Non-root nextjs user execution protects your host system.
Setting Up a Health Check Endpoint
Orchestrators and reverse proxies need a lightweight endpoint to verify application health without running database queries on every ping.
Create `src/app/api/health/route.ts`:
typescript
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json(
{
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
},
{ status: 200 }
);
}
```Nginx Reverse Proxy and SSL Configuration
Place Nginx in front of your Next.js container to handle SSL termination, static file caching, and gzip/brotli compression.
Create `/etc/nginx/sites-available/nextjs.conf`:
nginx
upstream nextjs_upstream {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 80;
server_name example.my.id www.example.my.id;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.my.id www.example.my.id;
ssl_certificate /etc/letsencrypt/live/example.my.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.my.id/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Cache Next.js static assets
location /_next/static/ {
proxy_pass http://nextjs_upstream;
proxy_cache_valid 200 365d;
proxy_set_header Host $host;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Main application proxy
location / {
proxy_pass http://nextjs_upstream;
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_read_timeout 60s;
}
}
```Obtain free SSL certificates using Certbot:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.my.id
Deploying to Your VPS with M.A.F Cloud
Instead of manually SSH-ing to configure systemd services, firewall rules, and container lifecycles, manage your Next.js containers with M.A.F Cloud.
Install the node agent on your VPS with one command:
curl -fsSL https://cexi.my.id/agent-install.sh | sudo MAFCLOUD_TOKEN=<token> MAFCLOUD_API=https://cexi.my.id bash
Once connected, open the M.A.F Cloud dashboard and set up your application server:
1. Click 'Create Server' and select the Node.js template
2. Allocate port 3000 with TCP protocol
3. Set memory limits (512 MB for basic sites, 1024 MB for sites with Server Components and SSR)
4. Enable crash auto-restart to recover from unhandled exceptions automatically
M.A.F Cloud provides built-in port probing to verify that your Next.js instance is reachable, live WebSocket logs for instant debugging, and automated snapshot backups.
Performance Tuning for Production
Apply these optimizations to squeeze maximum throughput from modest hardware.
### Memory Management
Node.js defaults to allocating up to 1.5 GB of RAM. On a 1 GB or 2 GB VPS, limit the V8 heap to prevent out-of-memory kernel kills:
NODE_OPTIONS="--max-old-space-size=768" node server.js
### Image Optimization Considerations
The default Next.js `next/image` optimization uses Sharp, which consumes noticeable CPU on dynamic image resizing. On resource-constrained VPS instances:
Pre-optimize images during build time where possible
Set up an external image proxy (like Cloudflare Images or an S3 compatible bucket)
Configure cache TTLs in next.config.js to avoid re-optimizing identical assets repeatedly
### Environment Variables and Secrets
Never bake sensitive keys into your Docker image. Pass environment variables at runtime via Docker Compose or your panel settings:
yaml
version: "3.8"
services:
web:
image: nextjs-app:latest
restart: unless-stopped
ports:
- "3000:3000"
env_file:
- .env.production
environment:
- NODE_ENV=production
```Summary and Maintenance Checklist
Running Next.js on your own VPS gives you lower latency, zero vendor lock-in, and full architectural flexibility. Regular maintenance keeps everything running smoothly.
Review container logs weekly for unhandled promise rejections
Monitor memory utilization trends with panel metrics
Renew SSL certificates automatically via Certbot cron jobs
Test disaster recovery by restoring from automated snapshots