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.

Want to learn from my journey building online businesses? Join my newsletter.

No spam. Unsubscribe at any time.

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 →.

2025

2024

2023

© 2023-2025 Ryan Chiangryanschiang.com