What YOURLS needs from your server
- Apache 2.4 or newer with mod_rewrite, per the requirements page. Nginx works too: the docs print a server block for it.
- PHP 8.1 or newer, plus the cURL extension if you will use the API. The repository's composer.json also asks for the pdo_mysql, DOM, filter, hash and PCRE extensions.
- MySQL 5.5+ or MariaDB 10.0+ for your links and their click log.
- HTTPS, which the docs recommend for every install.
You won't find a memory or CPU figure in the docs. They say instead that a redirect costs almost no CPU and four database queries, two reads and two writes. So for us, the thing to get right is a fast local disk under the database rather than a large processor. One privacy point before you start: each counted click saves who clicked as far as the server can tell, meaning IP, referring page, browser string and country (the yourls_log_redirect function in the code). Setting YOURLS_NOSTATS to true switches that logging off, statistics included.
The server that fits a link shortener
A shortener is one job, and that is what the small cartridges on our dedicated server page are for: the machine is all yours, you log in as root, and nobody meters the port, so a link that gets shared widely never turns into a bandwidth bill. If your shortener will live beside other sites, a one-processor everyday server is the usual home, the same kind of box our WordPress guide starts from.
- A ready server is yours within 30 minutes of the payment being approved; one built to order takes hours instead.
- You get a console screen and power button you can open yourself on rack servers, on blades, and on two cartridge models, the m510 and m710x. That rescues you if a firewall change shuts SSH. Every other cartridge has the power button but no screen.
- One IPv4 address is included and IPv6 costs nothing, so your short domain can answer on both.
- You manage the server; we keep the hardware and the network running.
Leave out the eight-core X-Gene ARM cartridge here. It only runs Ubuntu 16.04 or 18.04, and this guide is written for Ubuntu 24.04 LTS.
Getting the files and the database in place
Start with DNS: point your short domain at the server (an A record, plus AAAA for IPv6) so it resolves well before you ask for a certificate. Then paste this as root. It turns the firewall on with only SSH allowed, setting IPV6=yes before the first rule so that rule covers IPv6 too. Apache begins listening on port 80 during the install, but nobody outside can reach it yet.
# as root on a fresh Ubuntu 24.04 LTS server
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
ufw allow OpenSSH
ufw --force enable
apt update && apt install -y apache2 libapache2-mod-php php-mysql php-curl php-xml mariadb-server certbot python3-certbot-apache
a2enmod rewrite
DBPASS=$(openssl rand -hex 24)
mariadb <<SQL
CREATE DATABASE yourls CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'yourls'@'localhost' IDENTIFIED BY '$DBPASS';
GRANT ALL PRIVILEGES ON yourls.* TO 'yourls'@'localhost';
SQL
V=1.10.6 # replace with the newest tag on github.com/YOURLS/YOURLS/releases
mkdir -p /srv/yourls
curl -fsSL https://github.com/YOURLS/YOURLS/archive/refs/tags/$V.tar.gz | tar -xz --strip-components=1 -C /srv/yourls
C=/srv/yourls/user/config.php
cp /srv/yourls/user/config-sample.php $C
chown root:www-data $C && chmod 660 $C
sed -i -e "s|your db user name|yourls|" -e "s|your db password|$DBPASS|" \
-e "s|https://your-own-domain-here.com|https://go.example.com|" \
-e "s|modify this text with something random|$(openssl rand -hex 32)|" $C
Now open /srv/yourls/user/config.php in nano and change the sample 'username' => 'password', line to the login and long password you want. While you are in there:
YOURLS_SITEis your short address: lowercase, no slash at the end, and always typed the same way in your browser (with www or without, never both).YOURLS_PRIVATEstays true, which keeps your admin area behind the login.YOURLS_COOKIEKEYgot a random value from the last line. It protects your login cookie and your API token.YOURLS_URL_CONVERTpicks short codes in lowercase only (36) or in both cases (62); the docs say not to change it once you have links.- Your database password reached MariaDB through standard input, out of sight of
ps, but the sed line holds it, along with the freshly made cookie key, as arguments for an instant. If other people can log in to this machine, leave those two out of the sed and type both values in by hand instead.
Apache, .htaccess and an install only you can see
YOURLS sends every short code through yourls-loader.php, and on Apache that is the job of the .htaccess file the docs give for a site at the domain root. You write it yourself, because the installer can't write into a folder owned by root. The virtual host is our own minimal one.
# file: /etc/apache2/sites-available/yourls.conf (ours)
<VirtualHost *:80>
ServerName go.example.com
DocumentRoot /srv/yourls
<Directory /srv/yourls>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# file: /srv/yourls/.htaccess (as published in the YOURLS docs)
# BEGIN YOURLS
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ /yourls-loader.php [L]
</IfModule>
# END YOURLS
The installer page asks for no password (admin/install.php has no login check in it), so you finish it while port 80 is still closed to the world:
- Switch sites:
a2ensite yourls,a2dissite 000-default, thenapache2ctl configtest && systemctl restart apache2. - On your own computer, tunnel in with your usual login:
ssh -N -L 8888:localhost:80 you@go.example.com. - Open
http://localhost:8888/admin/install.php, that exact address (plain/admin/gets a 307 redirect to install.php at yourYOURLS_SITEaddress, set in includes/Config/Init.php, and that https address isn't live yet), and click Install YOURLS. If it says .htaccess couldn't be created, that's expected: yours is already in place.
HTTPS and your first sign-in
- Let web traffic through with
ufw allow 80/tcpandufw allow 443/tcp. These get IPv6 copies too, because IPV6=yes came first. - Get the certificate:
certbot --apache -d go.example.com -m you@example.com --agree-tos --no-eff-email --redirect --non-interactive. Certbot obtains it first, then writes the HTTPS virtual host that uses it, plus the redirect from http. Those flags answer in advance the questions a first run would otherwise ask: your email, acceptance of Let's Encrypt's terms (read them first) and whether to share the email with the EFF. - Sign in at
https://go.example.com/admin/. Going by the code in includes/auth.php, a successful sign-in is the moment YOURLS swaps your clear-text password for aphpass:hash in config.php. - Make sure it happened (
grep -c phpass: /srv/yourls/user/config.phpshould print 1 or more), then make the file read-only:chmod 440 /srv/yourls/user/config.php. To change a password later, make it writable for a moment again, as the docs describe.
Plugins and the API for your scripts
Each plugin is a folder in user/plugins holding a plugin.php, switched on from Manage Plugins in your admin area. A handful ship with YOURLS, and a community list is kept on GitHub; the docs say plainly that a listing is no promise the code is sound, so add only what you need. If something breaks afterwards, their advice is to turn every plugin off and back on one by one.
To shorten links from your own tools, call yourls-api.php with an action such as shorturl, expand, url-stats, stats, db-stats or version, and ask for json, xml, jsonp or (for some actions) simple output. Instead of your password, use the secret signature token from the Tools page: it only works for the API, so it can't open your admin area. Send it in a POST body so it stays out of Apache's access log, for example curl -s -d format=json -d action=shorturl -d signature=TOKEN --data-urlencode url=https://example.com/spring-sale https://go.example.com/yourls-api.php (your shell history and ps will still see it).
- Want a token that expires? Hash a Unix timestamp joined to your token with sha256 and send
signature,timestampandhash=sha256. The code accepts it for 12 hours either side of its timestamp, 43,200 seconds (yourls_get_nonce_life, in functions-auth.php), unless you changeYOURLS_NONCE_LIFE. One line of the docs says an hour; the code says otherwise. - The code builds your token from your username and the cookie key, so picking a new password won't replace it. To retire a token, the docs say to change
YOURLS_COOKIEKEY. Expect to be signed out everywhere as well: functions-auth.php derives your login cookie from that same key. - You can make the statistics pages public with
YOURLS_PRIVATE_INFOSset to false, or the API withYOURLS_PRIVATE_API, and keep the admin private. A fully public shortening form comes as a sample file,sample-public-front-page.txt, and the docs warn it attracts spam.
Backups, restores and updates
Three things are yours and can't be downloaded again: your link database, plus user/config.php and the user/plugins folder (we add your .htaccess). This cron file saves them every night; end it with a newline. The dump is written to a plain .sql file first and compressed only if mariadb-dump succeeded, because cron's /bin/sh has no pipefail and a failed dump piped straight into gzip would still count as success. The archives include your database password, so keep them private, and copy them to another machine yourself, because a copy on the same disks is lost with them.
# file: /etc/cron.d/yourls-backup (our choice: 03:15 every night, ten days kept)
15 3 * * * root umask 077; mkdir -p -m 700 /var/backups/yourls && mariadb-dump --single-transaction yourls > /var/backups/yourls/links-$(date +\%F).sql && gzip -f /var/backups/yourls/links-$(date +\%F).sql && tar -czf /var/backups/yourls/links-$(date +\%F).tar.gz -C /srv/yourls user/config.php user/plugins .htaccess && find /var/backups/yourls -type f -mtime +10 -delete
- Before restoring, confirm both archives for that day are there:
D=2026-10-20; B=/var/backups/yourls; sudo test -s $B/links-$D.sql.gz && sudo test -s $B/links-$D.tar.gz && sudo zcat $B/links-$D.sql.gz | tail -1 | grep -q '^-- Dump completed' && sudo tar -tzf $B/links-$D.tar.gz > /dev/null && echo both-present. Stop unless you see both-present. - Be clear about what that check tells you: the files exist, aren't empty, unpack cleanly, and the dump reached its final completion comment. It can't tell you the data will load; restoring onto a spare machine is the only real proof.
- Bring the database back:
sudo sh -c "zcat $B/links-$D.sql.gz | mariadb yourls". - Unpack the saved files:
sudo tar -xzf $B/links-$D.tar.gz -C /srv/yourls.
When your admin area announces a new version, the docs' routine is: back up the database, unpack the latest release on top of your current files, then load /admin/; if the database needs upgrading, the upgrade check in includes/Config/Init.php sends you to the upgrade page. Here that means setting V to the new tag and rerunning the curl ... | tar line from the install. The release has no config.php, so yours stays as it is; it does include user/plugins with the plugins that ship with YOURLS, which get refreshed, while the plugin folders you added are left alone.
Rather use Docker?
There is an image called yourls among Docker's Official Images, and it belongs to the YOURLS project: Docker Hub lists YOURLS and its containers repository as the maintainer. The default tag runs Apache on container port 8080; the fpm and fpm-alpine tags are for setups with a separate web server, and the image page warns never to publish those to the internet. Instead of editing config.php you pass the settings in as environment variables, such as the site address, admin login and password and the database details, or their _FILE versions, which read each value from a file so it doesn't show in docker inspect.
- The compose example on Docker Hub maps
8080:8080. With no address in front, Docker publishes that port on every address of the server, 0.0.0.0 and [::], and on IPv4 its rules skip ufw entirely. For IPv6, that only applies once Docker's IPv6 networking is turned on; otherwise docker-proxy handles the [::] side, and ufw filters it. Write it as127.0.0.1:8080:8080and let a web server on the host, such as Nginx Proxy Manager, sit in front; from Docker Engine 28 on, that address answers only the server itself. - Set
YOURLS_COOKIEKEYyourself: the image's config file (config-container.php in YOURLS/containers) falls back to the sample's placeholder when it is missing, and the Hub page doesn't mention it. - Give plugins their own volume, mounted where the image keeps them, so they survive a new container.
- Behind a proxy, each click is recorded under the proxy's address, not the visitor's, until a small plugin marks that address as trusted, as shown on the docs' proxy page.
What the MIT licence means for you
YOURLS is released under the MIT licence (the LICENSE file in its repository, copyright The YOURLS Contributors). This is how we read it in general terms, not legal advice: you can use it for yourself or your business, change it and even sell it, and you don't have to share your changes. If you hand copies to anyone else, whether you changed them or not, the copyright line and the permission notice must go with them. A Docker image also bundles other software from its base system, each under its own licence. If you'd like visitor analytics for the pages your links point to, our Matomo guide covers that next.
Questions people ask
- Can my whole team use it?
- You can give each person a login by adding more username and password pairs to config.php, and each gets an API token of their own. YOURLS itself has no roles, so treat every login as an administrator.
- My short links show a 404. What did I miss?
- Almost always the rewrite rules: on Apache, check that mod_rewrite is on, that the virtual host allows .htaccess with AllowOverride All, and that the .htaccess file sits in the YOURLS folder. The docs call this a configuration problem rather than a bug.
- Can I keep strangers from making links on my domain?
- Keep
YOURLS_PRIVATEtrue and don't install the sample public page. Then only signed-in users and API calls with your credentials or token can create links. If you do open it up, the docs recommend anti-spam plugins and limiting which protocols are allowed.