Skip to main content
Security & Reliability · Blog 29

Next.js 16 Docker & VPS Hardening

Protect the host, SSH, firewall, reverse proxy, container runtime, private services, secrets, logs, backups, and deployment pipeline as one production system.

A protected Docker application server surrounded by a firewall, access key, containers, and layered security shields

Blog #28 covered SSRF prevention at the application and outbound-network boundary. This article widens the view to the full production host. Strong input validation cannot compensate for public Redis, an exposed Docker socket, a leaked deployment key, or an unpatched VPS.

Next.js VPS security is a system property. A secure deployment reduces public entrypoints, limits every identity and process, separates public and private traffic, keeps credentials out of images and logs, detects failure, and can be rebuilt from trusted sources. None of these steps makes a server “fully secure”; together they reduce likelihood and impact.

Start with the Production Architecture

A conventional single-VPS design exposes Traefik on HTTP and HTTPS, keeps Next.js behind it, and places PostgreSQL, Redis, and workers on private Docker networks. Administrative SSH is a separate, restricted path. The host firewall, Docker firewall rules, reverse proxy, and application policy must agree on what is public.

Diagram 1: Hardened production VPSInternet traffic reaches only the host firewall and reverse proxy; Next.js and data services stay behind explicit network boundaries.
InternetHost firewallTraefik :80/:443
Next.jsPostgreSQL + Redis + worker
An isometric firewall and reverse proxy permit normal web traffic into a private container network while hostile paths are blocked
Keep the data plane private. The proxy is an ingress gate; application, database, cache, and worker containers communicate on deliberately scoped networks.

Defense in Depth

Hardening is not one command. The VPS, SSH service, firewall, Docker daemon, container, application, secrets store, proxy, CI/CD system, monitoring, and backups are independent layers. Assume any single control can be misconfigured. Reduce the privileges and reachable assets behind it.

Diagram 2: Defense-in-depth layersAn external request crosses multiple independent controls before reaching application data.
Internet edgeHost + SSH + firewallTraefik + TLSContainer least privilegeNext.js + private data services

Harden the VPS Host

Choose a supported operating-system release and define an update process for security fixes, Docker Engine, the kernel, OpenSSH, and the reverse proxy. Remove software and services the deployment does not need. Review local users, sudo access, startup services, listening sockets, time synchronization, disk capacity, and file permissions. A smaller host is easier to inventory.

Updates need operational discipline: observe vendor advisories, test changes, keep a rollback or rebuild path, schedule required restarts, and confirm that the service returns healthy. Automatic updates can reduce exposure time, but only when the organization understands reboot and compatibility behavior.

Reduce attack surface

Disable unused daemons, remove unnecessary packages, restrict administration, and keep only intentional network listeners.

Preserve recoverability

Record configuration, protect backups, and be able to recreate the host instead of depending on manual, undocumented state.

Secure SSH Without Locking Yourself Out

Use a named administrator or deployment account rather than routine root login. Prefer public-key authentication, protect private keys with appropriate controls, review authorized_keys, and limit sudo to the operations the role requires. Where practical, restrict SSH by VPN or trusted source ranges and monitor authentication failures.

Lockout warning: add and test the key in a second terminal before disabling password or root access. Run sudo sshd -t before reloading SSH, keep the current verified session open, and understand your provider console or recovery path.

Diagram 3: Administrative SSH pathA protected key and restricted network path authenticate a named administrator before minimal sudo elevation.
Admin device + keyVPN / trusted sourceNamed SSH userMinimal sudo

Firewall Rules and Docker Networking

A host firewall should deny unexpected inbound traffic and allow only the real administrative and web entrypoints. The safe order matters: verify the active SSH port, allow it, inspect the planned rules, then enable the firewall while a working session remains open. HTTP may remain only to redirect to HTTPS; HTTPS is the primary public service.

Docker creates firewall and NAT rules for published ports. Docker's documentation warns that traffic to published container ports can be diverted before UFW's normal chains, so “UFW enabled” is not proof that every ports: mapping is filtered as expected. Avoid publishing internal ports, bind narrowly when publication is required, inspect the effective rules, and use Docker's documented filtering points for your environment.

Diagram 4: Host firewall versus Docker publicationContainer port publishing can create a host-reachable path that must be reviewed alongside the host firewall.
Host pathInternet → UFW → host service
Published container pathInternet → Docker rules → containerReview explicitly

Private Container Networks

Blog #21 introduced the Compose application and data services. In production, publish only the proxy ports. Traefik and Next.js share an ingress network; Next.js and necessary internal services share a private application network; a database or cache is not attached to public ingress merely for convenience.

Conceptual Compose network boundary
services:
  web:
    expose: ["3000"]
    networks: [proxy, private]
  db:
    networks: [private]
  redis:
    networks: [private]

networks:
  proxy: {}
  private:
    internal: true

expose does not publish a port to host interfaces. ports does. An internal network can be useful, but test required traffic such as migrations, observability, or managed backups. Network isolation complements database and Redis authentication; it does not replace them.

Diagram 5: Private container networkThe proxy can reach Next.js, while only approved application services share the private data network.
TraefikNext.js
PostgreSQLRedisWorker

Run the Application with Least Privilege

A container root user is not identical to host root, but it has more power inside the container and can make a runtime escape or mounted-volume mistake more damaging. Use a dedicated non-root runtime identity when compatible. Make writable paths explicit, avoid broad bind mounts, and verify ownership during the image build.

Do not enable privileged: true for a web application. Drop unnecessary Linux capabilities where the image works without them, prevent privilege escalation where supported, and consider a read-only root filesystem with small writable mounts for known runtime needs. Each control must be tested against image optimization, temporary files, caches, and telemetry.

Diagram 6: Container least privilegeA non-root process receives only the filesystem, network, capability, and volume access the application requires.
Non-root app user
Required filesRequired networkRequired writable paths
Reduced impact

Build a Smaller Runtime Image

Use a multi-stage Dockerfile: install locked dependencies and compile in builder stages, then copy only the production output into a minimal runtime stage. Pin a reviewed base-image version or digest according to your update strategy. “Latest” weakens reproducibility; a permanent pin without updates becomes stale.

Next.js output: 'standalone' can produce a smaller server bundle suitable for a runtime image. Copy the standalone output together with required public and static assets, and test all runtime paths. A careful .dockerignore should exclude Git data, local dependencies, test output, development files, logs, and local secrets from the build context.

Diagram 7: Multi-stage image buildDependencies and source enter the builder, while the runtime receives only the tested production artifact and required assets.
Locked dependenciesBuilder + testsStandalone outputMinimal runtime

Keep Secrets Out of Images and Browsers

Blog #16 explains the Next.js server/client environment boundary. Variables prefixed with NEXT_PUBLIC_ are intended for browser exposure and must never contain secrets. Runtime secrets belong in a protected deployment mechanism with narrowly scoped access and a rotation plan.

Do not copy .env into the image, commit it, print it, or pass secrets with Docker build arguments. Docker's current guidance recommends BuildKit secret or SSH mounts for credentials needed only during a build because build arguments and environment instructions are inappropriate for sensitive data. A build-time secret must not be copied into the resulting filesystem.

Diagram 8: Safe secret flowProtected deployment storage provides a server-only secret at runtime; it never enters source, image layers, browser bundles, or logs.
Protected secret storeRuntime injectionServer-only process
Never browserNever logs

Protect the Docker Socket

Control of the Docker daemon is a host-level trust boundary. Never mount /var/run/docker.sock into the Next.js application. A compromised application with daemon control may create privileged containers, mount host paths, read other workloads, or alter the deployment.

Traefik's Docker provider commonly needs metadata from the daemon. Treat that access as highly privileged, keep Traefik minimal and patched, set exposedByDefault=false, and consider a carefully restricted socket proxy only after assessing what API operations Traefik needs. A read-only filesystem mount flag on the socket does not make the Docker API read-only.

Diagram 9: Docker socket trust boundaryThe application is denied daemon access; an explicitly reviewed discovery path is isolated from the workload.
Next.jsNo socket
Traefik discoveryRestricted, reviewed path
Docker API

Read-Only Filesystems, Volumes, and Capabilities

Make immutability practical rather than ceremonial. Set the root filesystem read-only where compatible, then add only named volumes or temporary filesystems required by the process. Mount configuration and certificates read-only where the consuming service does not need to modify them. Never bind broad host directories such as the filesystem root.

Diagram 10: Controlled filesystem writesThe runtime image is immutable while narrowly defined temporary and persistent paths remain writable.
Read-only root+tmpfs for temporary data+Named persistent volumeExplicit writes only

Keep PostgreSQL and Redis Private

Do not publish database or Redis ports to the public Internet. Use private container networks, strong service credentials, least-privilege database roles, protected backups, and encryption where the threat model or network path requires it. Blog #22 covers Redis use in the application; a cache may still hold sensitive or security-relevant data.

Diagram 11: Private data planeOnly the services with a defined need can reach PostgreSQL, Redis, or the worker; no host publication creates a public path.
Next.js
DB networkCache network
WorkerNo public ports

Traefik, TLS, and Security Headers

Blog #20 introduced Docker and Traefik. In production, expose only required HTTP/HTTPS entrypoints, redirect HTTP to HTTPS, automate certificate renewal, protect account keys, monitor certificate failures, and restrict or disable the dashboard. Traefik documents api.insecure=true as development-only.

Review which proxies are trusted to set forwarded headers; accepting them from arbitrary clients can confuse scheme, client IP, redirects, rate limits, and audit trails. Blog #17 explains the application proxy boundary; it does not replace the edge proxy or host firewall. Security headers may be configured in Next.js, Traefik, or another platform. Pick clear ownership to avoid duplicate or conflicting policies. Dynamic CSP nonce work normally belongs at application/request level, as explained in Blog #26.

Diagram 12: TLS termination and private upstreamTraefik terminates HTTPS and forwards only reviewed proxy headers to a private Next.js service.
Browser HTTPSTraefik TLSTrusted proxy headersNext.js :3000 private

SSRF Still Matters on Private Networks

Private networking removes direct Internet exposure; it does not mean the application cannot reach internal services. If attacker input controls a server-side destination, an SSRF flaw could turn Next.js into a path toward a private HTTP service. Combine the validation model from Blog #28 with service authentication, segmentation, egress restrictions, and least privilege.

Diagram 13: SSRF and the Docker networkOutbound validation permits approved public requests and blocks or restricts destinations on private networks.
Attacker inputNext.js outbound validation
Public allowedInternal blocked / restricted

Resources, Health Checks, and Restarts

Set memory, CPU, process, request, and concurrency limits where the platform and workload support them. Values are workload-specific: load-test the full stack, observe headroom, and avoid copying arbitrary limits. An overly small memory cap can create restart loops; no cap can let one runaway workload pressure every service.

A health endpoint should confirm availability without returning credentials, environment dumps, stack traces, or topology. { "status": "ok" } may be enough for a liveness signal. Separate readiness only when dependencies and orchestration behavior justify it. Blog #25 covers availability and scaling. Restart policies improve recovery from failure; they are not a security boundary, and monitoring must detect repeated restarts.

Safe Logging and Rotation

Never log passwords, session cookies, authorization headers, private keys, database URLs, API secrets, or complete environment dumps. console.log(process.env) is dangerous in production. Redact at the application boundary, use structured events, restrict log access, and define retention.

Diagram 14: Safe logging pipelineApplication events pass through redaction before entering structured logs and monitoring; secrets never enter the pipeline.
Application eventRedactionStructured logMonitoringSecrets: never log

Container logs can exhaust disk. Choose a supported logging driver or external collector and configure size/retention limits appropriate to traffic, incident response, and compliance. Monitor disk usage and validate the current Docker logging configuration rather than pasting an unreviewed daemon-wide example.

Monitoring and Alerting

Blog #24 explains observability. For a VPS, monitor uptime, latency, CPU, memory, disk, inode use, container health and restarts, HTTP 5xx, authentication failures, unusual outbound requests, TLS renewal, and backup failures. Monitoring does not prevent an attack; it shortens detection and response time.

Diagram 15: Production observabilitySignals from Next.js, Traefik, Docker, and the host become logs and metrics that drive alerts and investigation.
Next.jsTraefikDocker + VPS
Logs + metricsMonitoringAlert / investigate

Backups and Restore Testing

Back up the database, persistent uploads, critical configuration, and the deployment information needed to rebuild. Do not treat running containers as the primary backup; rebuild them from reviewed source and trusted images. Protect backup credentials, encrypt storage where appropriate, maintain an off-host copy and retention policy, restrict access, and monitor every job.

A backup is not trustworthy until restoration has been tested. Schedule restore exercises into an isolated environment, verify data integrity and application behavior, record recovery time, and fix missing dependencies before an incident.

Diagram 16: Backup and restore assuranceProduction data is copied to protected off-host storage, then periodically restored and verified.
Production dataEncrypted backupProtected off-host storageRestore test

Secure CI/CD and SSH Deployment

No GitHub Actions workflow was found in this repository. If the real project uses Actions, declare the smallest permissions each workflow or job needs; contents: read is a useful baseline only when sufficient. Protect production environments, review who can approve deployment, use narrowly scoped and rotatable credentials, and pin or otherwise trust action dependencies according to policy.

For SSH deployment, use a dedicated deployment user and key, restrict commands and sudo where practical, keep the private key in protected CI secrets, never commit it, and rotate it after suspected exposure. Prefer short-lived identity federation where the hosting environment supports it. Do not deploy as root by default.

Diagram 17: CI/CD deployment boundaryReviewed source becomes a scanned image; a protected environment authorizes a narrowly scoped deployment identity.
Reviewed commitBuild + test + scanProtected environmentDedicated deploy identity
A central application container protected by concentric security walls and controls for identity, secrets, data, monitoring, and recovery
Resilience needs several rings. Identity, isolation, secrets, monitoring, data protection, and recovery reinforce one another.

Images and Dependency Supply Chain

Use trusted base-image sources, review tags and digests, rebuild for relevant patches, and scan images where supported. A report showing zero known vulnerabilities does not prove an image safe; scanners depend on inventories and advisory data and do not discover every logic flaw or malicious behavior. Keep runtime packages minimal and preserve provenance.

Container hardening cannot repair a vulnerable npm dependency. Continue with Blog #30: Dependency Security & Supply Chain for lockfiles, dependency updates, audit limitations, Dependabot, action dependencies, and supply-chain controls.

What If the Server Is Compromised?

Isolate the affected system without destroying useful evidence. Preserve appropriate logs and snapshots according to the response plan, rotate potentially exposed credentials, identify the entry path and scope, and rebuild from trusted source rather than assuming ad-hoc cleanup removed everything. Restore validated data, patch the root cause, review accounts and keys, and monitor the rebuilt environment closely.

Diagram 18: Defensive incident responseContainment preserves evidence, credential rotation and root-cause analysis inform a trusted rebuild and monitored recovery.
DetectIsolate + preserveRotate + investigateTrusted rebuildValidate + monitor

Audit the Existing Deployment

AreaDetected configurationRiskRecommendation
DockerfileNot detectedDeployment cannot be assessedAudit the real application repository.
Container userNot detectablePrivilege level unknownVerify a non-root runtime identity where compatible.
PortsNo container config detectedPublication unknownExpose only proxy/administrative entrypoints.
NetworksNo Compose networks detectedIsolation unknownSeparate ingress and private data paths.
SecretsIgnored root .env exists; values not inspectedRuntime handling unknownKeep values out of Git, images, browser bundles, and logs.
TraefikNot detectedDashboard/TLS/socket posture unknownAudit the actual proxy configuration.
Health checksNot detectedAvailability signal unknownAdd a minimal non-sensitive endpoint when useful.
CI/CDNo .github/workflows detectedDeployment controls unknownReview the real workflow and credentials.

Common Next.js Docker & VPS Security Mistakes

  • Running every service as root.
  • Publishing Next.js port 3000 publicly behind a proxy.
  • Publishing Redis or PostgreSQL.
  • Mounting the Docker socket into the app.
  • Using privileged: true.
  • Baking secrets into a Dockerfile or image.
  • Committing .env.
  • Putting secrets in NEXT_PUBLIC_*.
  • Disabling SSH access before testing a key.
  • Enabling UFW before allowing the real SSH path.
  • Assuming UFW automatically controls every Docker port.
  • Exposing the Traefik dashboard.
  • Using stale images or blind latest tags.
  • Shipping build tools in the runtime image.
  • Allowing unlimited logs.
  • Keeping no backups.
  • Never testing restoration.
  • Running without monitoring.
  • Ignoring SSRF because services are private.

Production Security Checklist

VPS

  • Supported OS and security updates
  • Administrative users and SSH keys reviewed
  • Root/password login policy tested safely
  • Firewall and time synchronization reviewed
  • Disk use monitored

Docker

  • Engine and images maintained
  • Non-root runtime reviewed
  • Multi-stage minimal image
  • .dockerignore protects context
  • No privileged mode or app socket
  • Capabilities, mounts, read-only mode reviewed
  • Limits, health, restarts, rotation reviewed

Network

  • Only necessary host ports
  • Next.js behind proxy
  • Database, Redis, worker private
  • Traefik dashboard restricted
  • Docker/UFW behavior verified
  • SSRF and egress boundary reviewed

Secrets & HTTPS

  • .env absent from Git and images
  • No secrets in NEXT_PUBLIC_*
  • CI/CD and deployment keys protected
  • Rotation process available
  • Valid TLS and renewal monitoring
  • Forwarded/security headers owned clearly

Operations

  • Monitoring and actionable alerts
  • Protected off-host backups
  • Successful restore test
  • Incident plan
  • Image and dependency update process

Safe Implementation Order

  1. Audit the actual deployment and back up configuration.
  2. Remove secret exposure and review public ports.
  3. Separate ingress and private networks.
  4. Review runtime user, image stages, .dockerignore, mounts, socket access, privileges, and capabilities.
  5. Review Traefik, TLS, forwarded headers, health checks, logging, and CI/CD.
  6. Build and test locally, validate Compose, and exercise the container.
  7. Review the complete deployment diff and recovery path before production.

No live infrastructure was changed. This publication update did not connect to a VPS, alter firewall or SSH rules, deploy a stack, or touch containers, networks, or volumes.

Frequently Asked Questions

How do I secure a Next.js 16 app on a VPS?

Patch the host, restrict administrative access, expose only the reverse proxy, isolate data services, run the app with minimal container privileges, protect secrets, enable TLS, and maintain monitoring and tested backups.

Is Docker secure enough for production?

Docker provides useful isolation, not complete security. Host hardening, network policy, least privilege, secrets management, updates, monitoring, and recovery controls are still required.

Should Next.js run as root inside Docker?

Usually no. Build as needed, then run the production process as a dedicated non-root user when the application and base image support it.

Should I expose port 3000 publicly?

Usually no. When Traefik or another reverse proxy is present, keep the application on a private container network and expose only the proxy entrypoints.

Should PostgreSQL be exposed publicly?

Not for a typical single-VPS application. Keep it on a private network and use an authenticated, encrypted administrative path when remote access is genuinely required.

Should Redis be exposed publicly?

No for the architecture described here. Keep Redis private, require authentication where supported by the design, and restrict which services can reach it.

How should I secure SSH on Ubuntu?

Use a dedicated administrator account, key-based authentication, restricted source networks where practical, minimal sudo access, current patches, and monitored authentication logs.

Should I disable SSH password authentication?

Often yes, but only after key authentication has been tested in a second session and recovery access is understood. Validate the SSH configuration before reloading it.

Can UFW protect Docker containers?

Do not assume every Docker-published port follows the UFW policy you expect. Docker manages firewall rules; review the effective rules and avoid publishing internal service ports in the first place.

What is the difference between ports and expose?

Compose ports publishes a container port to host interfaces. Expose documents or makes a port available to connected containers without publishing it to the host by itself.

Should I mount the Docker socket into Next.js?

No. Docker daemon control is a host-level trust boundary. The application should not receive the socket; even proxy access must be narrowly assessed.

Where should Next.js production secrets be stored?

Provide server-only secrets at runtime through a protected deployment mechanism. Restrict access, rotate them, and never place secret values in NEXT_PUBLIC variables.

Can I put secrets in Docker build arguments?

Do not use build arguments or Dockerfile environment instructions for secrets. Docker documents secret mounts for build-time credentials because arguments and environment variables can persist in image metadata or layers.

What is a multi-stage Docker build?

It uses separate build and runtime stages so compilers, source files, and development dependencies do not all enter the final production image.

Should I use Next.js standalone output with Docker?

It is often useful because it creates a smaller deployable server bundle, but test image optimization, public assets, runtime variables, and all application features before production.

How do I secure Traefik?

Expose only required entrypoints, redirect to HTTPS, protect or disable the dashboard, set exposedByDefault to false, review forwarded-header trust, and treat Docker API access as highly privileged.

Do private Docker networks prevent SSRF?

No. They reduce public exposure, but a compromised or SSRF-vulnerable application may still reach services on its networks. Combine segmentation with outbound validation, service authentication, and egress controls.

How should I monitor a Next.js VPS?

Monitor uptime, CPU, memory, disk, container restarts, HTTP errors, authentication failures, outbound anomalies, certificate renewal, and backup results.

What should I back up?

Back up databases, persistent uploads, critical configuration, and enough deployment state to rebuild. Containers themselves are replaceable artifacts, not the primary backup.

How often should Docker images and dependencies be updated?

Use a documented, recurring process based on supported releases and risk. Test updates before release, respond quickly to relevant security fixes, and avoid both unpinned latest tags and permanently stale images.

Current Official References

Next Steps

Apply this guide to the real deployment as an audit, not a paste-ready command list. Record what is public, which identity owns each process, where secrets enter, which services can communicate, how failures are detected, and how the system will be rebuilt. Then change one layer at a time with verified access and recovery.

WhatsApp