Hosting SvelteKit

I’ve been running my side project Critical Notes, built with SvelteKit, on my own server for about a year now and it’s been pretty much rock solid. Since I saw some questions about how to host a SvelteKit app on your own server, I figured I’d document my setup.

Attention: In 2023 I’ve written a new article detailing how to setup a Debian server from scratch for running SvelteKit and Django apps. It’s much more detailed and in-depth, and this article below should no longer be used as a reference.

Nginx

The first part of the equation is the Nginx configuration for the website. Basically all it does is forward the request to the Node server coming with SvelteKit. It also redirects requests critical-notes.com to www.critical-notes.com.

/etc/nginx/sites-available/www.critical-notes.com

server {
    server_name www.critical-notes.com;
    root /var/www/www.critical-notes.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffer_size 16k;
        proxy_buffers 4 16k;
        proxy_busy_buffers_size 16k;
        client_max_body_size 20m;
    }

    listen 80;
}

server {
    server_name critical-notes.com;
    return 301 http://www.critical-notes.com$request_uri;
    listen 80;
}

The folder /var/www/www.critical-notes.com that is used in the config file above is an empty folder, the website isn’t actually served from there. All that Nginx does, is forward to the request to Node, which serves the site from the real site root, /opt/www.

Get HTTPS working with Certbot

Right now the website is only accessible using HTTP requests on port 80, which is not so good. Luckily getting HTTPS support is as easy and running one simple command:

$ certbot --nginx

Afterwards your config will looks more like this:

server {
    server_name www.critical-notes.com;
    root /var/www/www.critical-notes.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffer_size 16k;
        proxy_buffers 4 16k;
        proxy_busy_buffers_size 16k;
        client_max_body_size 20m;
    }

    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/api.critical-notes.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/api.critical-notes.com/privkey.pem; # managed by Certbot
    ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = www.critical-notes.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name www.critical-notes.com;
    listen 80;
    return 404; # managed by Certbot
}

server {
    server_name critical-notes.com;
    return 301 https://www.critical-notes.com$request_uri;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/critical-notes.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/critical-notes.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = critical-notes.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name critical-notes.com;
    return 404; # managed by Certbot
}

Restart Nginx to active the changes: service nginx reload.

SvelteKit server

Within my real site root /opt/www I can checkout my Git repo, and run npm run build, this creates the compiled build and the Node server. I am using adapter-node for this.

To run the Node server, I am using systemd. I created a config file /etc/systemd/system/www.critical-notes.com.service with the following content:

[Unit]
Description=www.critical-notes.com

[Service]
User=criticalnotes
Group=criticalnotes
ExecStart=node /opt/www/build/index.js
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

With that file in place I can run the Node server with the command service www.critical-notes.com start.

Deploying changes

Whenever I make changes to my website, I SSH into the server, git pull the latest changes, then run npm run build && service www.critical-notes.com restart to build the site and restart the Node server. And presto, the changes are live. Of course you can make this as fancy as you like with build scripts and other automations.