YOURLS is the old-school option in the best possible sense.

It is a small PHP application that does one job: run your own URL shortener. You get an admin panel, short-link creation, click statistics, an API, bookmarklets, and a plugin system without adding Redis, ClickHouse, queue workers, object storage, or a serverless deployment model.

If you want the lightest practical self-hosted shortener with a long history, YOURLS is still worth considering.

What is YOURLS?

YOURLS stands for “Your Own URL Shortener”.

I inspected commit:

9add23549f4ef2d1a8a2c883778a8a60b3e64d07

The current branch reports:

1.10.7-dev

The latest release tag in the checked-out repo was:

1.10.6

For the Docker setup, I pinned the image to:

yourls:1.10.6-apache

What You Get

YOURLS includes the practical shortener basics:

  • private admin interface;
  • short-link creation and editing;
  • custom keywords;
  • click counters;
  • per-link statistics pages;
  • referrer and visitor reports;
  • bookmarklets;
  • REST-like API through yourls-api.php;
  • plugin architecture;
  • sample public front-end and public API files;
  • support for running private or public instances.

The UI is not modern SaaS-style, but it is functional and fast. This is a small admin tool, not a marketing analytics suite.

Architecture

YOURLS is a traditional PHP web app.

The stack is:

  • PHP 8.1+ application code.
  • Apache in the official Docker image used here.
  • PDO MySQL database access.
  • MySQL or MariaDB for persistence.
  • Server-rendered admin UI.
  • Plugin hooks for extending core behavior.
  • API endpoint at yourls-api.php.

The request shape is simple:

Browser/API client -> YOURLS PHP app -> MariaDB

There are no required background workers, queue services, Redis caches, OLAP engines, or object-storage buckets.

Home-Lab Compose Review

There was already a YOURLS folder in Home-Lab.

The old setup worked as a rough reference, but it had avoidable problems:

  • duplicated compose files;
  • hardcoded database passwords;
  • hardcoded LAN IP;
  • latest image tags;
  • old 8080:80 container-port mapping;
  • old Compose version field;
  • no database healthcheck.

I replaced it with a single canonical compose file using .env, pinned images, MariaDB healthchecks, and the current official image port.

Self-Hosting with Docker

The reusable Home-Lab compose file is here:

The site includes the same Compose file from the local snippets folder:

assets/snippets/yourls/docker-compose.yml
#https://dbt3ch.com/books/yourls/page/docker-compose-stack

version: '3.8'

services:
  db:
    image: mariadb:latest
    container_name: yourls-db
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: yourls
      MYSQL_USER: yourls
      MYSQL_PASSWORD: yourlsdbpassword
    volumes:
      - yourls-db-data:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'

  yourls:
    image: yourls:latest
    container_name: yourls
    depends_on:
      - db
    ports:
      - "8080:80"  # Expose YOURLS on port 8080 of the host
    environment:
      YOURLS_SITE: http://192.168.3.200:8080 #http://localhost:8080
      YOURLS_DB_PASS: yourlsdbpassword
      YOURLS_DB_USER: yourls
      YOURLS_DB_NAME: yourls
      YOURLS_DB_HOST: db  # Use the service name of the MariaDB container
      YOURLS_SKIP_INIT: "false"
    volumes:
      - yourls-data:/var/www/html

volumes:
  yourls-data:
  yourls-db-data:

# version: '3.1'

# services:
#   yourls:
#     image: yourls:latest
#     container_name: yourls
#     depends_on:
#       - db
#     ports:
#       - "8080:80" # Expose YOURLS on port 8080 of the host
#     environment:
#       YOURLS_SITE: http://localhost:8080
#       YOURLS_DB_PASS: yourlsdbpassword
#       YOURLS_DB_USER: yourls
#       YOURLS_DB_NAME: yourls
#       # You can set the following to "true" to skip the install if this is a re-deployment
#       YOURLS_SKIP_INIT: "false"
#     volumes:
#       - yourls-data:/var/www/html # Persist your data here

#   db:
#     image: mysql:5.7
#     container_name: yourls-db
#     environment:
#       MYSQL_ROOT_PASSWORD: rootpassword
#       MYSQL_DATABASE: yourls
#       MYSQL_USER: yourls
#       MYSQL_PASSWORD: yourlsdbpassword
#     volumes:
#       - yourls-db-data:/var/lib/mysql # Persist database data here
#     command: '--default-authentication-plugin=mysql_native_password' # Needed for MySQL 8+

# volumes:
#   yourls-data:
#   yourls-db-data:


#https://github.com/YOURLS/YOURLS
#self hosted URL shortener in PHP 
#https://yourls.org/
#https://hub.docker.com/_/yourls

#https://github.com/YOURLS/awesome

#https://www.youtube.com/watch?v=tXM-csqijdw
#https://www.youtube.com/watch?v=WNVVlV75hs4 #one DB multiple containers

Prepare the environment:

cd assets/snippets/yourls
cp .env.sample .env

Generate secrets:

openssl rand -base64 32
openssl rand -base64 32
openssl rand -base64 32

Use those generated values for:

YOURLS_PASS
MARIADB_ROOT_PASSWORD
MARIADB_PASSWORD

Start the stack:

docker compose up -d

The default local admin URL is:

http://localhost:8789/admin/

On the first run, go to:

http://localhost:8789/admin/install.php

and click the install button.

Domain Configuration

YOURLS_SITE must match the public URL you will actually use, without a trailing slash.

Local examples:

YOURLS_SITE=http://localhost:8789
YOURLS_SITE=http://192.168.1.2:8789

Public example:

YOURLS_SITE=https://s.example.com

For a public deployment, put YOURLS behind HTTPS:

https://s.example.com -> reverse proxy -> http://yourls:8080

If YOURLS_SITE does not match the browser-facing URL, generated short links and asset URLs can be wrong.

API Notes

YOURLS exposes API actions through:

/yourls-api.php

Common API use cases include:

  • creating a short URL;
  • requesting JSON, XML, or plain text output;
  • checking link statistics;
  • using a signature token instead of sending the password directly;
  • adding custom API actions through plugins.

For private instances, keep the API private unless you have a specific public-use case.

Plugin Model

YOURLS has a long-standing plugin API.

Plugins live under:

user/plugins

That is one reason the compose file persists:

/var/www/html

so plugins, uploaded custom files, and generated config survive container replacement.

For a cleaner production setup, I would keep custom plugins under version control and mount only the plugin directories you intentionally manage.

Field Test

I smoke-tested the fixed Home-Lab compose with disposable containers and volumes.

Command shape:

YOURLS_PORT=8789 \
YOURLS_SITE=http://localhost:8789 \
YOURLS_USER=admin \
YOURLS_PASS=test-admin-password \
MARIADB_ROOT_PASSWORD=test-root-password \
MARIADB_PASSWORD=test-db-password \
docker compose -f /home/jalcocert/Desktop/Home-Lab/yourls/docker-compose.yml -p yourls_smoke up -d

Observed:

  • MariaDB 11.4 became healthy.
  • YOURLS copied its application files into /var/www/html.
  • GET / returned HTTP 403, which is expected before adding a public front page.
  • GET /admin/ returned HTTP 307 to /admin/install.php.
  • GET /admin/install.php returned HTTP 200.
  • POST /admin/install.php completed the install with HTTP 200.
  • GET /admin/ returned HTTP 200 after installation.
  • MariaDB contained the expected tables: yourls_log, yourls_options, and yourls_url.

Then I removed the smoke-test containers and volumes:

docker compose -p yourls_smoke down -v --remove-orphans

YOURLS is the simplest traditional option.

Compared with Shlink:

  • YOURLS has a built-in admin UI.
  • Shlink has a stronger API/CLI-first backend model.
  • Shlink has a separate web client.

Compared with Kutt:

  • YOURLS is older and simpler.
  • Kutt feels more like a complete modern web app.
  • YOURLS has a large plugin ecosystem.

Compared with Dub:

  • YOURLS is tiny.
  • Dub is a cloud-native attribution platform.
  • YOURLS needs PHP plus MySQL/MariaDB; Dub expects several specialized services.

See also:

FAQ

Does YOURLS have a UI?

Yes. YOURLS has a built-in admin UI at /admin/.

Why does / return 403?

That is normal for the official Docker image unless you add a public front page. Use /admin/ for management, or create a public page using the sample files if you want visitors to create links.

Does YOURLS require MySQL or MariaDB?

Yes. YOURLS stores links, options, and visit logs in a MySQL-compatible database. The Home-Lab snippet uses MariaDB.

Does YOURLS require Redis?

No. Redis is not part of the default YOURLS stack.

What additional services does YOURLS use?

The default Home-Lab stack uses only:

YOURLS
MariaDB

Additional infrastructure is optional:

  • MariaDB/MySQL: required database layer. MariaDB and MySQL are self-hostable; MariaDB is the FOSS default I used here.
  • Reverse proxy: optional but recommended for HTTPS. Caddy, Nginx, Traefik, and Apache are all self-hostable open-source options.
  • Plugins: optional. Plugin quality and dependencies vary by plugin; YOURLS itself does not require a plugin service.
  • External analytics/marketing tools: not required by core YOURLS. Add them only through plugins if you want them.

YOURLS does not need ClickHouse, Redis, QStash, S3, object storage, or a cloud account for normal self-hosting.

Can YOURLS be public?

Yes, but decide what “public” means. You can expose only redirects and the admin UI behind authentication, or you can build a public front page that lets visitors create short links. For most home-lab uses, keep link creation private.

Can I run YOURLS behind Cloudflare Tunnel?

Yes. Put YOURLS behind your tunnel or reverse proxy, set YOURLS_SITE to the public HTTPS URL, and make sure /admin/ is protected with strong credentials.

Is YOURLS still maintained?

Yes. The checked-out repo has recent release tags up to 1.10.6, and the current branch points at 1.10.7-dev.

What is the simplest deployment?

One YOURLS container and one MariaDB container.

That is the setup in this post.

Verdict

YOURLS is the URL shortener I would choose when I want something classic, small, and boring in a good way.

It does not have the modern polish of Kutt, the API/CLI structure of Shlink, or the attribution pipeline of Dub. But it is easy to run, easy to understand, plugin-friendly, and perfectly fine for a personal or small-team branded short-link service.