Skip to content

Instantly share code, notes, and snippets.

@andriilive
Last active October 4, 2024 14:07
Show Gist options
  • Save andriilive/f6711bce1aa15f6a551bc8c81d5adfe2 to your computer and use it in GitHub Desktop.
Save andriilive/f6711bce1aa15f6a551bc8c81d5adfe2 to your computer and use it in GitHub Desktop.
Self-hosted next.js on VPS: caddy & pm2 setup

Self-hosted next.js on VPS: caddy & pm2 setup

StandWithUkraine

Guide on simpliest ever self-hosted next.js on VPS configuration. Designed to run on a really weak hardware.

  • Auto HTTPS & certificates renewal with caddy
  • Production-ready aplication run process management with pm2
  • Go live in 5 minutes

Try live: https://digitalandy.eu

VPS Requirements

My example is running on debian, free tier by vultr. Requirements are:

Minimal: 512 MB RAM, 10 GB SSD, 1 CPU, 0.5TB Bandwish / mo
Recommended: 1 GB RAM, 10 GB SSD, 1 CPU, 1TB Bandwish / mo

If you can afford a perfomant VPS, check EasyPanel setup instead

Free VPS for 1 year 🎁

Free Debian | 512 MB | 10 GB SSD | 2 TB Bandwish VPS early access program by vultr.com

  1. Register via my refferal
  2. Apply for Vultr Free Tier Program in your account

CleanShot 2024-09-22 at 04 09 57@2x

  1. Wait for program activation (regullary takes 1-2 days only)
  2. Deploy new server

Follow new instance configuration example:

telegram-cloud-photo-size-4-5965133091204874637-y

Type: Cloud Compute - Shared CPU
Location: Miami, Seattle, or Frankfurt
Image: Debian 12 x64
Plan: Regular Cloud Compute
SSH Key: upload your public key (cat ~/.ssh/id_rsa.pub)
Hostname example: s.example.com

It's recommended to use subdomain as hostname, like vps.example.com or server.example.com)

  1. Enjoy this VPS for free for 1 year period. Recommendations:
  • Bandwish usage notifications: turn on to don't be charged if you bypass the free limits qouta

CleanShot 2024-09-22 at 04 25 49@2x


Domain Configuration

Point your domain to a new vps IP by creating new records:

Wildcard example:

  • A @.example.com 11.11.11.11
  • A *.example.com 11.11.11.11
  • CNAME www.example.com example.com

Hostname / doman example:

  • A s.example.com 11.11.11.11
  • A @.example.com 11.11.11.11
  • CNAME www.example.com example.com

You will be able to use a hostname, e.g s.example.com instead of server's ip after change
DNS changes typically applyed in 30 minutes - 1 hour

Test the hostname record by sending ping from your machine:

ping s.example.com

VPS Configuration

The configuration goes under the root user.

SSH with ssh [email protected]
SFTP with Transmit app guide

Setup checklist

For experienced users. Use the below screenshots to configure required parts by yourself

  • configure git
  • configure ufw
  • install nvm and node 18
  • install pm2 via npm as global dependency
  • install caddy server
  • optionally, install gh, postgres, pnpm, bun

SwapFile

If you’re on Debian, the steps to resolve the memory issue (which is likely causing the Killed message during pnpm install) are still applicable. Here’s a Debian-specific guide to fix it:

Debian might not have sufficient swap space, and increasing it can help avoid memory-related crashes.

Check if Swap Space is Enabled:

First, check if you have swap space configured:

swapon --show

If you don’t see any output or if your swap is very small, create or increase the swap size:

sudo fallocate -l 2G /swapfile

# If fallocate is not available, use dd:
sudo dd if=/dev/zero of=/swapfile bs=1M count=4096

sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# Make the swap permanent (so it persists across reboots)
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Bashrc

User .bashrc and .profile files:

CleanShot 2024-09-22 at 06 21 27@2x

env.sh (exporting the GH_TOKEN var):

CleanShot 2024-09-22 at 06 19 07@2x


My git config

I use dedicated example_bot GH user with ssh keys from server added
Optionaly, set up gh cli, loggined via GIT_TOKEN + https helpers in .gitconfig

User gitconfig and gitignore:

CleanShot 2024-09-22 at 06 17 54@2x


My ufw config

CleanShot 2024-09-22 at 05 22 22@2x

Allow access from admin's IP adresses with ufw allow from 193.0.0.0


My node config

The node is setted up via nvm manager, see the nvm ls

CleanShot 2024-09-22 at 05 32 51@2x

Packages installed globally via npm:

CleanShot 2024-09-22 at 05 34 45@2x


process manager pm2 config:
# Setup the startup script
pm2 startup

# Link to pm2 account
pm2 link xxxx xxxx

# Status / monitor
pm2 status
pm2 monitor

pm2 status:

CleanShot 2024-09-22 at 05 47 02@2x

pm2 new npm script

# /root/example.com
APP_ROOT=${1:-$PWD}

cd "$APP_ROOT" || exit

APP_NAME="$(jq -r .name package.json)"

echo "PATH: $APP_ROOT, NAME: $APP_NAME"

pm2 start npm --name "$APP_NAME" -- start
pm2 save

pm2 new file

# /root/api.example.com
APP_ROOT=${1:-$PWD}

cd "$APP_ROOT" || exit

APP_FILE="$(jq -r .main package.json)"
APP_NAME="$(jq -r .name package.json)"

echo "PATH: $APP_ROOT, FILE: $APP_FILE, NAME: $APP_NAME"

pm2 start --name "$APP_NAME" "./$APP_FILE"
pm2 save

Don't foget to chmod +x /root/**/*.sh


Caddy config

Caddy running as daemon: https://caddyserver.com/docs/running#unit-files

⚠️ Caddy is running as a daemon, don't caddy stop | start!

Caddy help caddy --help or https://caddyserver.com/docs/caddyfile-tutorial

Configure server by editing file /etc/caddy/Caddyfile

# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.

(logging) {
	log {
		output file /var/log/caddy/caddy.log {
			roll_size 1mb
		}
		format json
	}
}

(optimization) {
	# Compress everything else that would benefit
	encode zstd gzip
}

www.example.com {
	redir https://example.com{uri}
}

api.example.com {
	import logging optimization
	reverse_proxy localhost:8080
}

example.com {
	import logging optimization
	reverse_proxy localhost:3000
}

:80, :443 {
	redir https://example.com permanent

	# Set this path to your site's directory.
	# root * /usr/share/caddy

	# Enable the static file server.
	# file_servers
	# encode gzip

	# Another common task is to set up a reverse proxy:
	# reverse_proxy localhost:8080

	# Or serve a PHP site through php-fpm:
	# php_fastcgi localhost:9000
}

#example.com:80, api.example.com:80 {
#    redir https://{host}{uri}
#}

# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

Logs are stored at /var/log/caddy default location
Logs cheatsheet https://caddy.community/t/making-caddy-logs-more-readable/7565/13

CheatSheet:

# LOGS
# view pretty log
tail -f /var/log/caddy.log | jq
# view all logs
bat /var/log/caddy/*.log* --paging=never --plain

# CONFIG
cd /etc/caddy/
caddy validate
caddy fmt --overwrite

# SERVICE
systemctl status caddy
# restart service
sudo systemctl restart caddy
# gracefully reload Caddy after making any changes:
sudo systemctl reload caddy

My current SSH connection with custom welcome message example:

CleanShot 2024-09-22 at 05 18 03@2x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment