Full Setup: Next.js Universal Template & Port Manager on HestiaCP

Full Setup: Next.js Universal Template & Port Manager on HestiaCP

Step 1.1: Create the HTTP Template (.tpl)

Location: /usr/local/hestia/data/templates/web/nginx/nextjs-universal.tpl

Command to create:

=========================================================================

Universal Next.js Web Domain Template

DO NOT MODIFY THIS FILE! CHANGES WILL BE LOST WHEN REBUILDING DOMAINS

https://hestiacp.com/docs/server-administration/web-templates.html

=========================================================================

server {
listen %ip%:%proxy_port%;
server_name %domain_idn% %alias_idn%;

error_log /var/log/apache2/domains/%domain%.error.log error;

# Increase client max body size for file uploads
client_max_body_size 100M;

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml+rss
    application/atom+xml
    image/svg+xml;

# Next.js proxy configuration
# PORT: Change this port for each domain/user
location / {
    proxy_pass http://127.0.0.1:3000;
    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;

    # Next.js specific headers
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;

    # Timeout settings for long-running requests
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    # WebSocket support for Next.js
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

# Next.js static files (if served from public_html)
location /static/ {
    alias /home/%user%/web/%domain%/public_html/static/;
    expires max;
    add_header Cache-Control "public, immutable";
}

# Next.js media files
location /media/ {
    alias /home/%user%/web/%domain%/public_html/media/;
    expires max;
    add_header Cache-Control "public, immutable";
}

# Next.js _next/static files (if served from public_html)
location /_next/static/ {
    alias /home/%user%/web/%domain%/public_html/_next/static/;
    expires max;
    add_header Cache-Control "public, immutable";
}

# Health check endpoint
location /health {
    proxy_pass http://127.0.0.1:3000;
    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;
}

include %home%/%user%/conf/web/%domain%/nginx.conf_*;

}

Step 1.2: Create the HTTPS/SSL Template (.stpl)

Location: /usr/local/hestia/data/templates/web/nginx/nextjs-universal.stpl

Command to create:

=========================================================================

Universal Next.js SSL Web Domain Template

DO NOT MODIFY THIS FILE! CHANGES WILL BE LOST WHEN REBUILDING DOMAINS

https://hestiacp.com/docs/server-administration/web-templates.html

=========================================================================

server {
listen %ip%:%proxy_ssl_port% ssl;
server_name %domain_idn% %alias_idn%;

ssl_certificate     /home/%user%/conf/web/%domain%/ssl/%domain%.pem;
ssl_certificate_key /home/%user%/conf/web/%domain%/ssl/%domain%.key;

error_log /var/log/apache2/domains/%domain%.error.log error;

# Increase client max body size for file uploads
client_max_body_size 100M;

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml+rss
    application/atom+xml
    image/svg+xml;

# Next.js proxy configuration
# PORT: Change this port for each domain/user
location / {
    proxy_pass http://127.0.0.1:3000;
    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;

    # Next.js specific headers
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;

    # Timeout settings for long-running requests
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    # WebSocket support for Next.js
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

# Next.js static files (if served from public_html)
location /static/ {
    alias /home/%user%/web/%domain%/public_html/static/;
    expires max;
    add_header Cache-Control "public, immutable";
}

# Next.js media files
location /media/ {
    alias /home/%user%/web/%domain%/public_html/media/;
    expires max;
    add_header Cache-Control "public, immutable";
}

# Next.js _next/static files (if served from public_html)
location /_next/static/ {
    alias /home/%user%/web/%domain%/public_html/_next/static/;
    expires max;
    add_header Cache-Control "public, immutable";
}

# Health check endpoint
location /health {
    proxy_pass http://127.0.0.1:3000;
    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;
}

include %home%/%user%/conf/web/%domain%/nginx.ssl.conf_*;

}
Step 1.3: Verify the files were created

ls -la /usr/local/hestia/data/templates/web/nginx/nextjs-universal*
You should see:

nextjs-universal.tpl (HTTP template)

nextjs-universal.stpl (HTTPS/SSL template)

Key Differences Between the Two Files:

Feature
.tpl (HTTP)
.stpl (HTTPS/SSL)

Port
%proxy_port%
%proxy_ssl_port%

SSL
No SSL
ssl enabled

Certificates
None
SSL certificates included

Include
nginx.conf_
nginx.ssl.conf_

Step 2.1: Create the port manager script

Location: /usr/local/bin/nextjs-port-manager.sh

!/bin/bash

# Next.js Port Manager for Multiple Users/Domains
# This script helps manage ports for different Next.js applications

set -e

# Configuration
PORTS_FILE="/etc/nextjs-ports.conf"
LOG_FILE="/var/log/nextjs-ports.log"

# Create ports file if it doesn't exist
if [ ! -f "$PORTS_FILE" ]; then

        echo "# Next.js Port Allocation" > "$PORTS_FILE"
        echo "# Format: USER:DOMAIN:PORT:SERVICE_NAME" >> "$PORTS_FILE"
        echo "# Example: abzal:mclabor.abzal.net:3000:mclabor-nextjs" >> "$PORTS_FILE"
        echo "" >> "$PORTS_FILE"

fi

# Function to get next available port
get_next_port() {
    local start_port=3000
    local max_port=3999

    # Read existing ports
    local used_ports=()
    if [ -f "$PORTS_FILE" ]; then
        while IFS=: read -r user domain port service; do
            if [[ "$port" =~ ^[0-9]+$ ]]; then
                used_ports+=($port)
            fi

            done < <(grep -v '^#' "$PORTS_FILE" | grep -v '^$')

fi

    # Find next available port

        for ((port=start_port; port<=max_port; port++)); do

if [[ ! " ${used_ports[@]} " =~ " ${port} " ]]; then
echo $port
return 0
fi
done

        echo "No available ports in range $start_port-$max_port" >&2

return 1
}

# Function to register a new Next.js app
register_app() {
    local user="$1"
    local domain="$2"
    local service_name="$3"

        if [ -z "$user" ] || [ -z "$domain" ] || [ -z "$service_name" ]; then
            echo "Usage: $0 register <user> <domain> <service_name>"

echo "Example: $0 register abzal mclabor.abzal.net mclabor-nextjs"
exit 1
fi

    # Check if already registered
    if grep -q "^$user:$domain:" "$PORTS_FILE"; then
        echo "App already registered for $user:$domain"
        grep "^$user:$domain:" "$PORTS_FILE"
        return 1
    fi

    # Get next available port
    local port=$(get_next_port)
    if [ $? -ne 0 ]; then
        echo "Error: $port"
        exit 1
    fi

    # Register the app

        echo "$user:$domain:$port:$service_name" >> "$PORTS_FILE"

echo "Registered: $user:$domain:$port:$service_name"

    # Log the registration

        echo "$(date): Registered $user:$domain on port $port" >> "$LOG_FILE"
        

echo "Port $port allocated for $domain"
echo "Update your nginx template to use port $port"
echo "Update your systemd service to use port $port"
}

# Function to list all registered apps
list_apps() {
    echo "Registered Next.js Applications:"
    echo "================================"
    if [ -f "$PORTS_FILE" ]; then
        while IFS=: read -r user domain port service; do
            if [[ "$port" =~ ^[0-9]+$ ]]; then
                echo "User: $user"
                echo "Domain: $domain"
                echo "Port: $port"
                echo "Service: $service"

                    echo "Status: $(systemctl is-active $service 2>/dev/null || echo 'Not installed')"

echo "---"
fi
            done < <(grep -v '^#' "$PORTS_FILE" | grep -v '^$')

else
echo "No applications registered"
fi
}

# Function to remove an app
remove_app() {
    local user="$1"
    local domain="$2"

        if [ -z "$user" ] || [ -z "$domain" ]; then
            echo "Usage: $0 remove <user> <domain>"

exit 1
fi

    # Get the port and service
    local line=$(grep "^$user:$domain:" "$PORTS_FILE")
    if [ -z "$line" ]; then
        echo "App not found for $user:$domain"
        return 1
    fi

        IFS=: read -r user domain port service <<< "$line"
        

# Stop the service if running
        if systemctl is-active "$service" >/dev/null 2>&1; then

echo "Stopping service $service..."
systemctl stop "$service"
systemctl disable "$service"
fi

    # Remove from ports file
    sed -i "/^$user:$domain:/d" "$PORTS_FILE"

    echo "Removed: $user:$domain:$port:$service"

        echo "$(date): Removed $user:$domain from port $port" >> "$LOG_FILE"

}

# Function to get port for a domain
get_port() {
    local user="$1"
    local domain="$2"

        if [ -z "$user" ] || [ -z "$domain" ]; then
            echo "Usage: $0 get-port <user> <domain>"

exit 1
fi

    local line=$(grep "^$user:$domain:" "$PORTS_FILE")
    if [ -z "$line" ]; then
        echo "App not found for $user:$domain"
        return 1
    fi

        IFS=: read -r user domain port service <<< "$line"

echo $port
}

# Function to create service file
create_service() {
    local user="$1"
    local domain="$2"
    local port="$3"
    local service_name="$4"

    local service_file="/etc/systemd/system/$service_name.service"
    local app_dir="/home/$user/web/$domain/public_html"

        cat > "$service_file" << EOF

[Unit]
Description=Next.js application for $domain
After=network.target

[Service]
User=$user
Group=$user
WorkingDirectory=$app_dir
ExecStart=$app_dir/node_modules/.bin/next start -p $port

# Environment variables
Environment="NODE_ENV=production"
Environment="PORT=$port"
Environment="PATH=$app_dir/node_modules/.bin:/usr/local/bin:/usr/bin:/bin"

# Restart configuration
Restart=always
RestartSec=3

# Security settings
NoNewPrivileges=true
PrivateTmp=true

# Resource limits
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

    echo "Service file created: $service_file"
    echo "Run: sudo systemctl daemon-reload"
    echo "Run: sudo systemctl enable $service_name"
    echo "Run: sudo systemctl start $service_name"
}

# Main script logic
case "$1" in
    "register")
        register_app "$2" "$3" "$4"
        ;;
    "list")
        list_apps
        ;;
    "remove")
        remove_app "$2" "$3"
        ;;
    "get-port")
        get_port "$2" "$3"
        ;;
    "create-service")

            if [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ] || [ -z "$5" ]; then
                echo "Usage: $0 create-service <user> <domain> <port> <service_name>"

exit 1
fi
create_service "$2" "$3" "$4" "$5"
;;
*)
echo "Next.js Port Manager"
echo "==================="
echo ""
echo "Usage:"
            echo "  $0 register <user> <domain> <service_name>  - Register new app"

echo " $0 list - List all apps"
            echo "  $0 remove <user> <domain>                  - Remove app"
            echo "  $0 get-port <user> <domain>                - Get port for domain"
            echo "  $0 create-service <user> <domain> <port> <service> - Create service file"

echo ""
echo "Examples:"
echo " $0 register abzal mclabor.abzal.net mclabor-nextjs"
echo " $0 list"
echo " $0 get-port abzal mclabor.abzal.net"
;;
esac

Step 2.2: Make the script executable

sudo chmod +x /usr/local/bin/nextjs-port-manager.sh

Step 2.3: Test the script

nextjs-port-manager.sh

Step 2.4: Verify the installation

# Check if the script is in PATH
which nextjs-port-manager.sh

# Check if the ports file was created
ls -la /etc/nextjs-ports.conf

# Check if the log file was created
ls -la /var/log/nextjs-ports.log

What the Port Manager Does:

✅ Manages ports - Automatically assigns ports 3000-3999

✅ Tracks applications - Keeps a database of all Next.js apps

✅ Creates services - Generates systemd service files

✅ Prevents conflicts - Ensures no two apps use the same port

✅ Easy management - Simple commands to register/list/remove apps

Available Commands:

nextjs-port-manager.sh register <user> <domain> <service_name> - Register new app

nextjs-port-manager.sh list - List all registered apps
nextjs-port-manager.sh get-port <user> <domain> - Get port for a domain
nextjs-port-manager.sh remove <user> <domain> - Remove an app
nextjs-port-manager.sh create-service <user> <domain> <port> <service> - Create service file




 



Step 3: Register Your First Next.js Application

  1. First, let's check if the port manager script is working:

1. Check the correct script name:

ls -la /usr/local/bin/nextjs-port-manager*

2. Register your first Next.js app using the correct script name:

nextjs-port-manager.sh register abzal mclabor.abzal.net mclabor-nextjs

This command will:

Register an app for user abzal

Domain mclabor.abzal.net

Service name mclabor-nextjs

3. Check what was created:

nextjs-port-manager.sh list

4. View the generated systemd service file:

cat /etc/systemd/system/mclabor-nextjs.service

Create the symlink:

ln -s /home/abzal/web/mclabor.abzal.net/public_html/.next /home/abzal/web/mclabor.abzal.net/public_html/_next