Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hosting

This page explains hosting liblin on your own VPS, which runs Debian / Ubuntu, and Nginx as a reverse proxy.

1. Download liblin JAR

Latest liblin releases could be found here https://codeberg.org/liblin/url_redirect/releases. Download the jar file, and let’s rename it to liblin.jar.

2. Install Java

On your server, run these commands:

sudo apt update
sudo apt install openjdk-25-jre-headless
java -version  # verify

3. Upload the JAR

From your local machine:

scp liblin.jar user@your-vps-ip:/home/user/

4. Run it manually first (sanity check)

java -jar liblin.jar

Your app likely starts on port 3000 by default. You can override it:

PORT=8080 java -jar liblin.jar

You can use browser to test your app at http://your-vps-ip:3000, or http://your-vps-ip:8080 if you set a custom port.

5. Set up a systemd service (so it runs forever)

Create a service file:

sudo nano /etc/systemd/system/liblin.service

Paste this:

[Unit]
Description=My Clojure Ring App
After=network.target

[Service]
User=user
WorkingDirectory=/home/user
ExecStart=/usr/bin/java -jar /home/user/liblin.jar
Restart=always
RestartSec=5
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable liblin
sudo systemctl start liblin
sudo systemctl status liblin

6. Set up Nginx as a reverse proxy

sudo apt install -y nginx

Create a config:

sudo nano /etc/nginx/sites-available/liblin
server {
    listen 80;
    server_name your-domain.com;  # or your VPS IP

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

Enable it:

sudo ln -s /etc/nginx/sites-available/liblin /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

6.1 Optional: caching redirects

liblin itself does not keep a link cache, but Nginx can cache the responses it gets from liblin. This reduces load on liblin and on the git host, and makes repeat redirects faster.

First define a cache zone in the http context. On Debian / Ubuntu, create /etc/nginx/conf.d/liblin-cache.conf:

proxy_cache_path /var/cache/nginx/libl
                 levels=1:2
                 keys_zone=libl_cache:10m
                 max_size=1g
                 inactive=2d
                 use_temp_path=off;

Create the cache directory and give Nginx permission to write to it:

sudo mkdir -p /var/cache/nginx/libl
sudo chown -R www-data:www-data /var/cache/nginx/libl

The zone is named libl_cache; that is the name used by proxy_cache below. Define it only once. 10m is the shared memory for cache keys, max_size=1g caps disk usage, and inactive=2d removes entries that have not been used for two days. The proxy_cache_path directive must be in the http context, so do not put it inside server or location.

Now add the cache directives to the location / block:

location / {
    proxy_pass http://localhost:3000;
    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_cache libl_cache;
    proxy_cache_valid 200 302 1d;   # cache pages and redirects for a day
    proxy_cache_valid 404 1m;       # cache "not found" only briefly
    proxy_cache_key "$scheme$request_method$host$request_uri";
    add_header X-Cache-Status $upstream_cache_status;
}
  • proxy_cache_valid 200 302 1d caches successful responses and redirects for one day. The 302 is the important one for liblin.
  • proxy_cache_valid 404 1m stops a typo from hitting liblin and the git host on every request, while letting a newly created link start working within a minute.
  • proxy_cache_key keeps separate entries for HTTP and HTTPS, different methods, different domains, and different paths or query strings.
  • add_header X-Cache-Status $upstream_cache_status shows whether a response came from the cache. MISS means Nginx fetched it from liblin, HIT means it came from cache, and EXPIRED means the cached copy was refreshed. Use add_header ... always; if you also want the header on 404 responses.

Reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Test with a real short link:

curl -s -o /dev/null -D - https://your-domain.com/alice/demo | grep -i x-cache-status

The first request should report MISS, and the second should report HIT. Use http:// instead of https:// if you have not enabled HTTPS yet.

A cached redirect is served until its TTL expires, so a change to a destination file, the RDL file, or the root index page may take up to 1d to appear. Lower the 200 302 TTL if you change links often, or clear the cache after updating:

sudo find /var/cache/nginx/libl -type f -delete

The cache is stored on disk, so redirect targets are kept on the server for the TTL. If that is not acceptable, disable caching or use a shorter TTL.

If you enable HTTPS later, verify that the location / block serving port 443 also has the cache directives. If Nginx reports unknown "libl_cache" cache zone, check that proxy_cache_path is in the http context and reload Nginx.

7. Open the firewall

sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable

Visit Certbot’s website for detailed instructions for your OS: https://certbot.eff.org/

Certbot will auto-edit your Nginx config and set up auto-renewal.

9. Setting up ENV variables

liblin reads one env var LIBLIN_DOMAINS which is a path to a edn file containing domain mappings. Lets say you assign /home/user/liblin/domains.edn to it, read the section Domain Slurps for more details.

10. Domain Slurps

liblin is designed to operate with custom domain names or even sub-domains. For that you need a edn file, which maps domains to what it needs to redirect to.

10.1 Redirecting for all codeberg users

For example https://libl.in/liblin/cli will read the content of the file https://codeberg.org/liblin/liblin/raw/branch/main/cli.txt, and will redirect to what ever is present in it.

In https://libl.in/liblin/cli, the liblin is the codeberg user name, and cli is the file name. If there is a repository called liblin by user liblin in codeberg, then the URL will redirect to URL present in https://codeberg.org/liblin/liblin/raw/branch/main/cli.txt.

To achieve it, you need to add the following to your edn file:

{
    "libl.in:80"
    "https://codeberg.org/{{ paths[0] }}/liblin/raw/branch/main/{{ paths[1] | index-if-empty }}.txt"
}

Where paths is can be considered as vector containing parts of URL after the domain name. In libl.in/liblin/cli, paths will be ["liblin", "cli"] which means paths[0] is liblin and paths[1] is cli. We will be writing about {{ }} in RDL (Redirect Language).

10.2 Redirecting for your domain

Let’s say there is username, or organization in codeberg named yu7 and it has a repository named liblin, and you want to point https://yu7.in/:short to read from the file in https://codeberg.org/yu7/liblin and redirect it to whaterver is present in it, you can configure it as follows.

{
    "yu7.in:80"
    "https://codeberg.org/yu7/liblin/raw/branch/main/{{ paths[0] | index-if-empty }}.txt"
}

We will soon be writing albout RDL (Redirect Language).

11. Deploying new versions

When you deploy a new version, just scp the new jar over and sudo systemctl restart liblin.

12. Useful commands after deployment

sudo systemctl restart liblin       # restart after uploading new jar
sudo journalctl -u liblin -f        # tail logs
sudo systemctl status liblin        # check status