Deploying a Secure Next.js App on a Linux VPS with Nginx & Docker

Published on Jan 2026 Β· 10 min read

Let’s go beyond development and start thinking like a DevOps engineer.

The goal of this article is to provide a simple and fast approach to setting up a secure Linux VPS to host a Next.js application using Nginx and Docker. For this guide, we’ll use AlmaLinux 9 on a basic Namecheap VPS.

πŸ”§ Prerequisites

πŸ–₯️ Install AlmaLinux 9 on VPS

Install AlmaLinux 9 using your hosting provider’s control panel. For this article, a Namecheap Basic VPS is used.

AlmaLinux Installation

Once installation is complete, open a terminal on your local machine.

πŸ” Generate SSH Key (Local PC)

ssh-keygen -t ed25519 -C "VPS-server"

When prompted, provide a name for the key:

/Users/YOUR_USER_NAME/.ssh/vps-server

Display the public key:

cat /Users/YOUR_USER_NAME/.ssh/vps-server.pub

πŸ”‘ Login to VPS (First Time)

ssh root@YOUR_SERVER_IP

After a successful login, you should see the server terminal.

VPS Login Screen

✍️ Install Nano Editor

sudo dnf install nano -y

πŸ”“ Configure SSH Key Authentication (VPS)

mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys

Paste the public key from your local machine.

Set correct permissions:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

From now on, you can log in without a password:

ssh -i /Users/YOUR_USER_NAME/.ssh/vps-server root@SERVER_IP

πŸ”₯ Firewall Configuration (firewalld)

sudo dnf install -y firewalld
sudo systemctl enable --now firewalld

Allow essential services (DO NOT skip SSH):

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

sudo firewall-cmd --reload
sudo firewall-cmd --list-all

⚠️ Warning: If SSH is not allowed, you will be locked out.

πŸ›‘οΈ Install Fail2Ban (Brute-force Protection)

While Cloudflare protects against DDoS attacks, server-level services such as SSH still require protection.

sudo dnf install -y epel-release
sudo dnf install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit configuration:

sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 3600

[nginx-http-auth]
enabled = true

[nginx-botsearch]
enabled = true
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status

πŸ”„ Enable Automatic Security Updates

sudo dnf install -y dnf-automatic
sudo systemctl enable --now dnf-automatic.timer
sudo systemctl status dnf-automatic.timer

πŸ”„ Set timezone

timedatectl list-timezones
sudo timedatectl set-timezone Asia/Colombo
Verify: timedatectl

🌐 Install Nginx

sudo dnf update -y
sudo dnf install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

πŸ“¦ Create Nginx Server Block

sudo nano /etc/nginx/conf.d/abc.com.conf
server {
    listen 443 ssl;
    server_name abc.com www.abc.com;

    ssl_certificate     /home/piyal/ssl/abc.crt;
    ssl_certificate_key /home/piyal/ssl/abc.key;

    location / {
        proxy_pass http://127.0.0.1:3023;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80;
    server_name abc.com www.abc.com;
    return 301 https://$host$request_uri;
}

πŸ”’ Install Cloudflare Origin SSL

Cloudflare β†’ SSL/TLS β†’ Origin Server β†’ Create Certificate

Cloudflare SSL Cloudflare SSL

nano /home/piyal/ssl/abc.crt
nano /home/piyal/ssl/abc.key
sudo nginx -t
sudo systemctl restart nginx

🐳 Install Docker on VPS

sudo dnf install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
docker --version

🧱 Dockerize Next.js App (Local Machine)

Create the Dockerfile on your project

FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["server.js"]

πŸ—οΈ Build & Transfer Image

docker build --platform linux/amd64 -t abc-com-app-amd64 .
docker save abc-com-app-amd64 | gzip > abc-com-app-amd64.tar.gz
scp abc-com-app-amd64.tar.gz root@SERVER_IP:/root/

▢️ Run Container on VPS

docker load < abc-com-app-amd64.tar.gz

docker run -d \
  --name navora \
  --restart unless-stopped \
  -p 3023:3000 \
  --read-only \
  --tmpfs /tmp:noexec \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --memory 1024m \
  --memory-swap 1024m \
  --cpus="1.0" \
  navora-app-amd64
docker ps

βœ… Final Result

πŸŽ‰ Your Next.js application is now live:

πŸ‘‰ https://abc.com