Analytics cookies only (Google Analytics, PostHog). No ad networks, no cross-site tracking. See our privacy policy.
Self-host YOURLS
updated Oct 2026deployed by usprices checked · Jul 2026Install verified on Ubuntu 26.04 · Oct 2026 · how we test
We earn commissions when you shop through the links below. Full disclosure →
Your Own URL Shortener: a PHP admin UI and API for creating short links on your own domain, with detailed per-link stats and a plugin system, and full control over your data. MIT-licensed; the Docker Official Image runs PHP on Apache and needs MySQL or MariaDB, configured entirely from environment variables.
Runs on a VPS from $6/mo (Kamatera 2GB, 2 GB RAM) · prices checked Sep 2026
Our recommendation
Pick YOURLS when you want short links on your own domain with the smallest possible moving parts: a PHP admin page to create links, detailed per-link stats, an API, and a plugin system for everything else, with your data in a database you own. It is MIT-licensed PHP that needs MySQL or MariaDB. Our snippet is the compose example from the Docker Official Image's README with the image pinned, MariaDB in place of the README's `mysql`, and generated passwords, including the admin password that `YOURLS_USER` / `YOURLS_PASS` turn into the first login. The project documents web server, PHP and database versions but no RAM minimum, so our 512 MB floor is an estimate. In the catalog, Kutt is a Node app with SQLite, custom domains and expiring links, and Shlink is an API-first PHP shortener with a separate web client; YOURLS is the oldest and plainest of the three, with the largest plugin ecosystem and a UI that ships in the box.
What you need
Any VPS with at least 512 MB of RAM
A domain you control — most self-hosted setups need HTTPS in front of them
Paid link — we earn a commission if you shop through it.
Install
Run these commands on your server:
# YOURLS — URL shortener in the Docker Official Image + MariaDB — based on the compose example in the yourls image README on Docker Hub, pinned to 1.10.6-apacheecho "DB_PASSWORD=$(openssl rand -hex 16)" > .envecho "DB_ROOT_PASSWORD=$(openssl rand -hex 16)" >> .envecho "YOURLS_PASS=$(openssl rand -hex 12)" >> .envcat > docker-compose.yml <<'EOF'services: yourls: image: yourls:1.10.6-apache restart: unless-stopped depends_on: - db ports: - 80:8080 environment: YOURLS_SITE: http://SERVER_IP YOURLS_USER: admin YOURLS_PASS: ${YOURLS_PASS} YOURLS_DB_HOST: db YOURLS_DB_NAME: yourls YOURLS_DB_USER: yourls YOURLS_DB_PASS: ${DB_PASSWORD} db: image: mariadb:11.4.13 restart: unless-stopped environment: MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MARIADB_DATABASE: yourls MARIADB_USER: yourls MARIADB_PASSWORD: ${DB_PASSWORD} volumes: - db_data:/var/lib/mysqlvolumes: db_data:EOFdocker compose up -dtimeout 180 bash -c 'until curl -fsS http://localhost/admin/ >/dev/null; do sleep 5; done'# → open /admin/ on port 80 to run the installer and sign in as admin with YOURLS_PASS from .env; the bare site root answers 403 by design, short links live at /<keyword>
The yourls image README and its container config flag these before you hand out a short link:
non-negotiableThe README says reaching the site root on first instantiation generates an error and to open /admin/ instead; that is where the installer runs and where you sign in with YOURLS_USER and YOURLS_PASS (ours is generated into .env; the README's example_password is replaced). The container's config.php is created from a template on first start, and the entrypoint replaces the YOURLS_COOKIEKEY placeholder with a random hash at that moment.
non-negotiableOnly the database volume (db_data) persists in the README's compose; the application directory, including user/config.php, plugins, pages and languages under /var/www/html/user/, is regenerated inside the container. The README says to mount a volume for persistent plugins, pages or languages, so add one before installing plugins. Upgrading is editing the image tag and docker compose up -d; the YOURLS upgrade finishes in the admin UI.
non-negotiableThe README's example uses the unpinned yourls and mysql images; we pin the Apache variant of the current release and the MariaDB LTS line (the project's requirements list MariaDB alongside MySQL). Only YOURLS_SITE, YOURLS_USER and YOURLS_PASS are required; YOURLS_DB_USER defaults to root and YOURLS_DB_HOST to mysql, which is why our compose sets them explicitly for the dedicated user and the db service. Every variable also has a _FILE form for Docker secrets.
non-negotiableApache listens on 8080 inside the container, mapped to 80 here, plain HTTP; terminate TLS in front. YOURLS_SITE is required and must be the exact URL people use, lowercase with no trailing slash, and the project's configuration docs warn that picking the www or non-www form means you must use that same form in the browser; replace the http://SERVER_IP placeholder before creating links, because every short URL is built from it. Set YOURLS_PRIVATE=false only if you really want a shortener anyone can use.