How to Deploy SvelteKit App to Nginx Reverse Proxy (With PM2 and SSL)

Updated

4 min read

How to Deploy SvelteKit App to Nginx Reverse Proxy (With PM2 and SSL)

Here's my simple process for deploying a SvelteKit app to a server running Nginx.

I will also show you how to set up SSL, PM2, and a reverse proxy.

Prerequisites

  • A server running Ubuntu or Debian
  • A domain name
  • A SvelteKit app
  • Node.js and npm installed on your server
  • Nginx installed on your server
  • PM2 installed on your server

Steps

1. Install SvelteKit Node adapter

Install @sveltejs/adapter-node for SvelteKit (learn more on SvelteKit adapters here):

npm i -D @sveltejs/adapter-node

Replace @sveltejs/adapter-auto with @sveltejs/adapter-node in your svelte.config.js file:

svelte.config.js
javascript-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';

export default {
	kit: {
		adapter: adapter()
	}
};

2. Load environment variables

Install dotenv:

npm i dotenv

According to SvelteKit documentation, in production .env files are not automatically loaded. So you'll need to run node with the flag -r dotenv/config to load environment variables.

Now is also a good time to set the port and host in your .env file:

.env
bashPORT=5173 # or whatever port you want for your reverse proxy
HOST=127.0.0.1

3. Build your app

Build your app for production:

npm run build

4. Set up Nginx server block

Here's a basic Nginx server block for SvelteKit and handling ACME challenges for Certbot to auto-renew SSL certificates.

Create a basic server block without SSL and we'll add SSL later.

This server block just serves static files for ACME challenges and redirects HTTP to HTTPS.

/etc/nginx/sites-available/example.com
bash# Redirect HTTP to HTTPS
# Serve static files for ACME challenges
server {
	listen 80;
	server_name example.com www.example.com;

	location ~ /.well-known/acme-challenge {
		root /var/www/certbot/example;
		allow all;
	}

	location / {
		return 301 https://example.com$request_uri;
	}
}

Replace example.com with your domain (that is pointed to your server's IP address with an A record).

Test Nginx config: sudo nginx -t

If everything checks out, restart Nginx: sudo systemctl restart nginx

5. Create Certbot directory

I've chosen to serve ACME challenges from /var/www/certbot, where each domain has its own directory, e.g. /var/www/certbot/example.

Create the directories:

cd /var/www
mkdir certbot
cd certbot
mkdir example # or whatever your domain is

6. Create Certbot SSL certificate

Now that we have a server block and directory for Certbot, we can create the SSL certificate.

sudo certbot certonly --webroot -w /var/www/certbot/example -d example.com -d www.example.com

(Optional) If you want to auto-renew SSL certificates, you can create a cron job:

crontab -e

Then add this line to the bottom of the file:

17 3,15 * * * certbot renew --quiet --post-hook "systemctl restart nginx"

This runs the command certbot renew twice a day at 3:17 AM and 3:17 PM. It's a best practice to renew certificates at a random time of the day (rather than midnight) to avoid overloading the Let's Encrypt servers.

The --post-hook flag restarts Nginx after the certificate is renewed.

7. Add SSL to Nginx server block

Now you can update your server block to include SSL:

/etc/nginx/sites-available/example.com
bash# Redirect HTTP to HTTPS
# Serve static files for ACME challenges
server {
    # What we had before...
}

# Configuration for HTTPS
# Reverse proxy for SvelteKit
server {
	listen 443 ssl;
	server_name example.com;

	ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

	location / {
		proxy_pass http://127.0.0.1:5174/; # Make sure the port matches your .env file
		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 90;
	}

	# if ($http_host != "example.com") {
	# 	return 444;
	# }
}

# Redirect www to non-www
server {
	listen 443 ssl;
	server_name www.example.com;
	ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
	return 301 https://example.com$request_uri;
}

Create a symbolic link: sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled

Then test your Nginx config: sudo nginx -t

If everything checks out, restart Nginx again: sudo systemctl restart nginx

8. Set up PM2 to run your app

Navigate back to your SvelteKit app directory.

Install PM2 if you haven't already: npm i -g pm2

Then run your app with PM2:

pm2 start node --name "process_name" -- -r dotenv/config build

Replace process_name with whatever you want to call your process, e.g. svelte_client.

The flags -r dotenv/config loads your .env file.

Lastly, save your PM2 config: pm2 save.

Wrapping Up

Your SvelteKit app should now be running and accessible from your domain.

This is a very basic setup, but it should be enough to get you started.

Some next steps may be setting up a firewall, configuring a CDN, or connecting your database.

Ryan Chiang

Meet the Author

Ryan Chiang

Hello, I'm Ryan. I build things and write about them. This is my blog of my learnings, tutorials, and whatever else I feel like writing about.
See what I'm building →.

Thanks for reading! If you want a heads up when I write a new blog post, you can subscribe below:

2024

2023

© 2023 Ryan Chiang|ryanschiang.com