DEV Community

Matthias Andrasch
Matthias Andrasch

Posted on


Host SvelteKit apps with SSR-support via (on Hetzner Cloud)

SvelteKit 1.0 was officially released a few hours ago 🥳. Time to dive into NodeJS and SvelteKit hosting. One key advantage of SvelteKit is that you can generate static sites as well as take advantage of Server Side Rendering (SSR). The best thing: You can mix these approaches.

Mix and match prerendered pages for maximum performance with dynamic server-side rendering for maximum flexibility.

In order to use the SSR features, you need to host your app as NodeJS application.

One option for this is to use the awesome server management service ploi. is like a nice graphical user interface for managing and configuring virtual private servers (VPS). Ploi does not host your site, it's just a middleware service connecting to your servers.

It basically offers three key features:

  1. Create and configure (virtual) servers
  2. Connect git repositories to these servers
  3. Deploy applications via deploy scripts

🇪🇺 An additional advantage is that ploi enables you to deploy NodeJS apps to various server providers like Hetzner Cloud. Hetzner offers servers which are located completely in the European Union (which is good for GDPR-compliance if you or your customers are located in the EU). I blogged about this here briefly.


Steps I won't cover in this tutorial:

Tutorial: Deploy and host SvelteKit

Create a new site on our server

First we need to create a new site on our server:

Screenshot ploi site dashboard, button create site is highlighted

Expand the advanced settings and use the following:

Screenshot ploi site dashboard, form for site generation with web directory to slash, project directory to slash, project type nodejs

(Set the project directory to /, otherwise you won't be able to request an SSL cert in the next steps)

Install project from a git repository

Afterwards we can install our project from a git repository:

Screenshot ploi site dashboard with install git button

I added my GitHub account and used my demo repo sveltekit-company-demo:

Screenshot github added

After adding a repository you can edit the deploy script of your new site:

Screenshot deploy script

Replace it with the following, we will need to add npm run build:

cd /home/ploi/
git pull origin main
# install
npm install
npm run build

# Server restarting is handled by plois NodeJS settings
echo "🚀 Application deployed!"
Enter fullscreen mode Exit fullscreen mode

SSL certificate

We will also need a SSL certificate. Thanks to the Lets's Encrypt initiative this is easily done nowadays:

Screenshot ploi request ssl certificate successful

NodeJS settings (pm2)

Now we need to make adjustments to the NodeJS settings. ploi will use pm2 by default to host our SvelteKit app, Supervisor is also possible.

Change the NodeJS settings to the following:

Screenshot ploi node js settings with PORT=3001 node build/index.js and port 3001 selected

Important: If you create your first site, it will run on port 3000. SvelteKits adapter-node assumes port 3000 by default. But if you add another site on a server, ploi will use a different port like 3001, 3002, etc. for each new project.

Therefore we need to make sure to set the env variable for the specific port correctly:

PORT=3001 node build/index.js

Thanks very much to aarondiel!

Switch to adapter-node

In order to run our SvelteKit Web App with NodeJS, we also need to apply the following changes to our projects source code.

We need to install adapter-node:

npm i -D @sveltejs/adapter-node

And we need to replace

import adapter from '@sveltejs/adapter-auto';


import adapter from '@sveltejs/adapter-node';

in svelte.config.js:

import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    kit: {
        adapter: adapter()
    preprocess: vitePreprocess()

export default config;
Enter fullscreen mode Exit fullscreen mode

See SvelteKits official docs for all information:

Adapters > Supported Environments > NodeJS

Commit and push these changes.

Deploy (to the build/-directory)

It is time to deploy our app. Let's hit the "Deploy now" button!

Screenshot deploy now button

You can check the status in the deployments section below:

Screenshot deployments section

Spawn the app with pm2

Our app is now successfully prepared in the build/ directory on the server, but we still need to start our app via the pm2 service.

Hit "Spawn" in the NodeJS settings dashboard:

Screenshot before spawn

You should see a brief success message and the buttons beneath should change as following:

Screenshot successful span

Congrats, your SvelteKit app is now live! 🥳🥳🥳

Screenshot live site

If you experience issues, see troubleshooting below.

Have I missed something or can this be optimized? How do you host SvelteKit apps? Let me know in the comments, much appreciated!

Further resources


You can check the NodeJS error log for information if something failed. In my case I wasn't aware I need to set the PORT=3001 if I use another port than 300.

Screenshot error logs

Investigating errors is also possible via SSH and pm2 list or pm2 logs command:

Screenshot pm2 list
See pm2 quickstart docs for more information.

What is pm2?

"PM2 is a production process manager for Node.js applications with a built-in load balancer. It allows you to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks." (pm2)

Other similiar tools are nodemon and supervisor.

Reboot configuration

If this is your first time using pm2 on your Hetzner server, you need to add pm2 to the $PATH for server reboots. Follow the How to make pm2 start on boot.

In my case I logged in to the Hetzner server ssh ploi@x.x.x.x via terminal / SSH. I previously added my laptops SSH key to ploi, see for more information.

Screenshot of ssh connection

I ran the command pm2 startup and pasted the suggested command (sudo env PATH=$PATH ...):

Screenshot terminal command from guide one

Screenshot terminal command from guide one

The root server password was retrieved via mail after the initial server completion (
Screenshot mail ploi

What is SSR?

SvelteKit hosting and GDPR

I blogged about this topic briefly:

How to host SvelteKit SSR apps (GDPR-compliant)?

In general: Big big kudos to Vercel for sponsoring the open source development of Svelte and SvelteKit in a massive way (! 🙏👏

Top comments (6)

kolja profile image

Great and thanks a lot.
Today I had my first try with Vercelli to deploy a SvelteKit app. Easy, but I concern about GDPR. So Hetzner might be a better way for German websites.
And now I have a nice step by step tutorial 🥰

mandrasch profile image
Matthias Andrasch

Nice! Have fun with the tutorial (and if you want let me know if it works for you as well). Cheers! :)

johannesmutter profile image
Johannes Mutter • Edited

A couple of issues I had with setting up Ploi NodeJs server with SvelteKit and how to resolve them:

  1. Adding SSL Certificate will fail if NodeJs is running (since the SvelteKit router won’t let you access ".well-known" directory where the temporary SSL keys are stored for Let’s Encrypt). So make sure you remove NodeJs while adding SSL certificates and hit "Spawn" afterwards again.

  2. I had to adjust the NGINX config for the site (not server) to make the node process of SvelteKit accessible, which in my case only worked if it was on Port 3000. The NGINX config can be found in Ploi at servers>sites>your site>manage>

# Ploi Webserver Configuration, do not remove!
include /etc/nginx/ploi/YOUR_WEBSITE_DOMAIN/before/*;

upstream app {

server {
  include /etc/nginx/ssl/YOUR_WEBSITE_DOMAIN;

  root /home/ploi/YOUR_WEBSITE_DOMAIN/;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers 'XXXXXXXXXXX';
  ssl_prefer_server_ciphers on;
  ssl_dhparam /etc/nginx/dhparams.pem;

  add_header X-Frame-Options "SAMEORIGIN";
  add_header X-XSS-Protection "1; mode=block";
  add_header X-Content-Type-Options "nosniff";

  charset utf-8;

  # logs can get big, check this folder from time to time
  access_log off;
  error_log  /var/log/nginx/YOUR_WEBSITE_DOMAIN-error.log error;

  # deny for security
  location ~ /\.(?!well-known).* {
    deny all;

  # required for let’s encrypt
  location ~ /.well-known/acme-challenge {  
    allow all;  
    root /var/www/html; 

  # Ploi Configuration, do not remove!
  include /etc/nginx/ploi/YOUR_WEBSITE_DOMAIN/server/*;

  # sveltekit node app
  location / {
    proxy_pass http://app;  # upstream
    proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    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-NginX-Proxy true;
    proxy_set_header X-Forwarded-Proto $scheme;

  # Serve static files
  location ~ ^/(robots.txt|humans.txt) {
    proxy_pass http://app;
    root /home/ploi/YOUR_WEBSITE_DOMAIN;
    access_log off;
    log_not_found off;
    expires 30d; # cache expiration
  location ~* \.(p?jpe?g|pjp|jfif|svg|tif|eps|bmp|a?png|gif|mp4|mov|heic|webp|avif|ico)$ {
    proxy_pass http://app;
    root /home/ploi/YOUR_WEBSITE_DOMAIN;
    access_log off;
    log_not_found off;
    expires 30d;

  # error pages are handled by SvelteKit, no need to add them here

# Ploi Webserver Configuration, do not remove!
include /etc/nginx/ploi/YOUR_WEBSITE_DOMAIN/after/*;
Enter fullscreen mode Exit fullscreen mode

Make sure to replace all the uppercase written text with your variables (e.g. YOUR_WEBSITE_NAME). Also don’t remove the config that Ploi generates for you.

mandrasch profile image
Matthias Andrasch • Edited

Hey, thanks for reply!

Regarding 2.), did you use PORT=3001 node build/index.js as start command when using another port? I got a working app on port 3001 with that and I haven't edited the nginx conf manually.

Image description

(But I'm not an expert on nginx confs yet, so don't know when these are updated by ploi after changes / what restarts are needed)

And I'm using latest SvelteKit

johannesmutter profile image
Johannes Mutter

So I just tried port 3001 with the start command PORT=3001 node build/index.js. It only works after manually setting proxy_pass http://localhost:3001; in NGINX config server > location. I guess the default behaviour for the feature "replace NGINX virtual host template" sets the proxy to 3000. Also the script doesn’t for example set the server names correctly if you have multiple domains & aliases, like in my case. That’s why I had to adjust the NGINX config manually.
Image description

Thread Thread
mandrasch profile image
Matthias Andrasch

Ah, interesting! Thanks for reply and insights!