YOURLS URL Shortener 1.10.6 on AWS User Guide
Overview
This guide covers launching and using the YOURLS URL Shortener 1.10.6 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
YOURLS ("Your Own URL Shortener") turns long links into short ones on your own domain, such as https://go.example.com/docs, and counts every click: when, from where (the referring page) and, if you add a GeoIP database, from which country. You create links in a web admin page or through an HTTP API that scripts, chat bots and CI jobs can call.
The cloudimg image runs YOURLS as a private service:
- HTTPS from the first boot. nginx serves YOURLS on port 443 with a certificate generated on your instance's first boot. Port 80 only redirects to HTTPS.
- A private admin and API. Only the
adminaccount can create links, see statistics or use the API (YOURLS_PRIVATEis on). Short links themselves redirect for everyone, which is what they are for. - No default login. The image contains no password, no API signature, no cookie key, no certificate and no host name. First boot generates all of them and writes them to
/var/lib/cloudimg/yourls-credentials.txt(root only). YOURLS' configuration holds only a bcrypt hash of the password. - Its own address, not the visitor's. YOURLS builds every short link from the site name in
/etc/cloudimg/yourls-site(your instance's public DNS name from the EC2 instance metadata, or your own domain). It never takes it from a request'sHostheader. - The title fetch is fenced in. When you add a link, YOURLS downloads the target page to read its title. On this image that download can only go to public internet addresses: YOURLS' own check is switched on for every user, and below it the kernel refuses connections from YOURLS to the EC2 instance metadata service, to the instance itself and to private address ranges, including your VPC's own address blocks.
- Database on a socket. MariaDB listens on its local socket only, in a network namespace of its own, and YOURLS signs in to it as its Linux user. There is no database password to manage or leak.
- No credentials in the logs. The access log records each request's path and status without its query string, and nginx's error log does not record requests, so an API signature or password sent in a URL never reaches the disk (Step 12).
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on, and PHP 8.3 at the fixed Ubuntu build for the 2026 PHP security advisories.
What is on the image: YOURLS 1.10.6 in /var/www/yourls, PHP 8.3 (php-fpm), MariaDB 10.11, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The build record and the licence notices are in /usr/share/doc/cloudimg-yourls/. YOURLS 1.10.6 fixes the security advisories published for 1.10.4 and earlier. The MaxMind GeoLite2 country database that YOURLS' source archive carries is not on the image (Step 9 says why and how to add your own).
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 443 (YOURLS), TCP 80 (redirects to HTTPS) and TCP 22 (SSH). Short links have to be reachable by the people who click them, so 443 is usually open to everyone; keep 22 open to your own networks only.
- Optionally a DNS name you control for your short links (Step 8).
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick YOURLS URL Shortener 1.10.6 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, a security group as above and a subnet with a public IP address.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=yourls}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| YOURLS URL Shortener 1.10.6 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot takes under a minute: it sets the site name, generates the certificate, the admin password, the cookie key and the API signature, creates the database, and only then starts php-fpm and nginx. Check that everything is running:
systemctl is-active yourls-firstboot yourls-egress-guard mariadb php8.3-fpm nginx \
&& sudo test -f /var/lib/cloudimg/yourls-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. php-fpm and MariaDB use local sockets and have no network port at all:
sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\.0\.0\.5[34]/ {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u | tee /dev/stderr \
| awk '{sub(/.*:/, "", $1); print $1}' | sort -un | xargs | grep -qx '22 80 443' && echo "network listeners: 22 80 443"
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
network listeners: 22 80 443
Step 4: Get your URL and credentials
Run sudo cat /var/lib/cloudimg/yourls-credentials.txt to see your admin URL, password and API signature. The file is readable by root only and holds these settings, all generated for this instance:
sudo sed -n 's/^\(YOURLS_[A-Z0-9_]*\)=.*/\1/p' /var/lib/cloudimg/yourls-credentials.txt | tee /dev/stderr \
| grep -qx YOURLS_API_SIGNATURE && echo "credentials present"
YOURLS_SITE
YOURLS_HOST
YOURLS_ADMIN_URL
YOURLS_API_URL
YOURLS_ADMIN_USER
YOURLS_ADMIN_PASSWORD
YOURLS_API_SIGNATURE
YOURLS_TLS_CERT
YOURLS_TLS_SHA256_FINGERPRINT
credentials present
YOURLS_SITE is https://<your instance's public DNS name>, the address every short link starts with. YOURLS_ADMIN_URL is the admin page, YOURLS_ADMIN_USER is admin, and YOURLS_API_SIGNATURE is the token scripts use instead of the password (Step 7). The instance's public and private IP addresses are served too, so you can also open the admin page by IP address.
The certificate is self-signed and generated on your instance, so your browser warns about it the first time. Compare the SHA-256 fingerprint the browser shows with YOURLS_TLS_SHA256_FINGERPRINT before you continue. For a certificate your browser trusts, use your own domain (Step 8).
Step 5: Sign in to the admin page
Open YOURLS_ADMIN_URL in your browser and sign in as admin with YOURLS_ADMIN_PASSWORD. Type a long URL into the box at the top, optionally a custom keyword, and choose Shorten The URL. The new short link appears in the table with its title and click count.


The same sign-in from the instance's own shell, to check it works. The password goes from the credentials file into a private temporary file that curl reads, so it is never printed or put on a command line:
H=$(sudo sed -n 's/^YOURLS_HOST=//p' /var/lib/cloudimg/yourls-credentials.txt)
C=(curl -s --cacert /etc/ssl/yourls/yourls.crt --resolve "$H:443:127.0.0.1")
umask 077; J=$(mktemp); P=$(mktemp)
sudo sed -n 's/^YOURLS_ADMIN_PASSWORD=//p' /var/lib/cloudimg/yourls-credentials.txt | tr -d '\n' > "$P"
N=$("${C[@]}" -c "$J" "https://$H/admin/" | grep -oE 'name="nonce" value="[A-Za-z0-9]+"' | cut -d'"' -f4)
"${C[@]}" -b "$J" -c "$J" -o /dev/null -w 'sign-in: %{http_code}\n' --data-urlencode username=admin \
--data-urlencode "password@$P" --data-urlencode "nonce=$N" "https://$H/admin/index.php" | tee /dev/stderr | grep -qx 'sign-in: 301' \
&& "${C[@]}" -b "$J" -o "$J.html" "https://$H/admin/index.php" && grep -q 'action=logout' "$J.html" && echo "signed in as admin"
RC=$?; rm -f "$J" "$J.html" "$P"; [ "$RC" -eq 0 ]
sign-in: 301
signed in as admin
A wrong password gets "Invalid username or password" and no session. YOURLS has no lockout, so a wrong guess never locks you out; the generated password is 24 random letters and digits, far beyond guessing. The session cookie is a __Host- cookie: sent over HTTPS only, to this host only, and never readable by page scripts.
Step 6: Shorten a link through the API
The API is the one described in YOURLS' API documentation. This example creates a short link with its own keyword, lets YOURLS fetch the page title, follows the short link and reads its click count. The signature is passed to curl from the credentials file, so it never appears on a command line:
H=$(sudo sed -n 's/^YOURLS_HOST=//p' /var/lib/cloudimg/yourls-credentials.txt)
C=(curl -s --cacert /etc/ssl/yourls/yourls.crt --resolve "$H:443:127.0.0.1")
umask 077; SF=$(mktemp)
sudo sed -n 's/^YOURLS_API_SIGNATURE=//p' /var/lib/cloudimg/yourls-credentials.txt | tr -d '\n' > "$SF"
K="guide$(date +%s)"
"${C[@]}" --data-urlencode "signature@$SF" -d format=json -d action=shorturl -d "keyword=$K" \
--data-urlencode "url=https://example.com/?from=cloudimg-guide-$K" "https://$H/yourls-api.php" \
| python3 -c 'import json, sys; d = json.load(sys.stdin); print("created:", d["status"], "| title:", d["title"]); sys.exit(d["status"] != "success" or d["title"] != "Example Domain")' \
&& "${C[@]}" -o /dev/null -w 'short link: %{http_code} -> %{redirect_url}\n' "https://$H/$K" | tee /dev/stderr | grep -q '^short link: 301 -> https://example.com/' \
&& "${C[@]}" --data-urlencode "signature@$SF" -d format=json -d action=url-stats -d "shorturl=$K" "https://$H/yourls-api.php" \
| python3 -c 'import json, sys; d = json.load(sys.stdin)["link"]; print("clicks:", d["clicks"]); sys.exit(int(d["clicks"]) != 1)'
RC=$?; rm -f "$SF"; [ "$RC" -eq 0 ]
created: success | title: Example Domain
short link: 301 -> https://example.com/?from=cloudimg-guide-guide1762520000
clicks: 1
Other useful actions are expand (the long URL behind a keyword), stats (top links) and db-stats (totals). Send requests as POST like this where you can. The API also accepts GET with the signature in the URL, which this image keeps out of its logs (Step 12), but a URL can still end up in your own shell history or tooling.
Step 7: Use the API from your own scripts
From another machine, call YOURLS_API_URL with signature=<YOURLS_API_SIGNATURE>. Copy the certificate once and check its fingerprint, then keep the signature in a curl config file rather than on the command line:
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/ssl/yourls/yourls.crt ./yourls.crt
openssl x509 -in yourls.crt -noout -fingerprint -sha256
printf 'data = "signature=%s"\n' '<YOURLS_API_SIGNATURE>' > yourls-api.cfg && chmod 600 yourls-api.cfg
curl --cacert yourls.crt -K yourls-api.cfg -d action=shorturl -d format=json \
--data-urlencode 'url=https://aws.amazon.com/marketplace/' https://<public-ip>/yourls-api.php
For a client you do not fully trust, YOURLS can use a time-limited signature instead of the fixed one: send timestamp=<unix time> and signature=<md5 of the timestamp followed by YOURLS_API_SIGNATURE>. That value stops working after 12 hours (YOURLS' default YOURLS_NONCE_LIFE), so a leaked URL cannot be replayed for long. Scripts can also send username=admin&password=... instead of a signature, which is best avoided: the password also opens the admin page.
Step 8: Use your own domain name
Point a DNS record (for example go.example.com) at the instance's public IP address, preferably an Elastic IP so it never changes, then set the name. The helper generates a new certificate for it and the instance's addresses, pins nginx to those names (other host names get no answer), switches YOURLS to https://<your-domain> and prints the new fingerprint. Existing short links keep working under the new name:
sudo yourls-set-site <your-domain>
For a certificate browsers trust, replace /etc/ssl/yourls/yourls.crt and /etc/ssl/yourls/yourls.key with your own (for example from Let's Encrypt; keep the key root only, mode 0600) and run sudo systemctl reload nginx. If you stay on the EC2 DNS name and the instance's public IP changes after a stop and start, run sudo yourls-set-site with the new name.
Step 9: Statistics and country lookups
Every short link has a statistics page: add a + to it (https://<your-domain>/docs+) while signed in. It shows clicks over time, referrers and, when YOURLS has a GeoIP database, countries.
YOURLS draws these charts with Google Charts, which your browser loads from www.google.com and www.gstatic.com when you open a statistics page (Google's terms do not allow the library to be hosted elsewhere). The image allows those two origins on the statistics pages only. The admin pages and the API keep a policy that loads nothing from other sites. If your browser cannot reach Google, the numbers on the statistics page still show; only the charts are blank.

YOURLS' source archive includes a copy of MaxMind's free GeoLite2 Country database. The GeoLite End User License Agreement requires everyone who holds a copy to delete each version within 30 days of MaxMind publishing a newer one, and to pass that duty on to anyone they give it to. A machine image can do neither, so cloudimg removed the database (MaxMind's licence and copyright notices are kept in /usr/share/doc/cloudimg-yourls/). Until you add one, the country of a click shows as unknown; everything else works.
To turn country lookups on, create a free MaxMind account, accept the GeoLite EULA, download GeoLite2-Country.mmdb (or install MaxMind's geoipupdate to keep it current, which the EULA requires) and copy it to the instance:
scp -i <your-key.pem> GeoLite2-Country.mmdb ubuntu@<public-ip>:/tmp/
ssh -i <your-key.pem> ubuntu@<public-ip> 'sudo install -o root -g root -m 0644 /tmp/GeoLite2-Country.mmdb /var/www/yourls/includes/geo/GeoLite2-Country.mmdb && rm /tmp/GeoLite2-Country.mmdb'
New clicks are then recorded with their country. The country always comes from the visitor's own connection: YOURLS on this image ignores X-Forwarded-For and CDN country headers, which anyone could forge. If you put a load balancer or CDN in front of the instance, every click shows the load balancer's address instead.
Step 10: Bookmarklets and links from other sites
YOURLS' admin page offers bookmarklets: browser buttons that shorten the page you are on by opening admin/index.php?u=<that page>. That address creates a link as soon as a signed-in browser opens it, so on this image a request to the admin pages that comes from another web site and carries a query string is refused with 403 before YOURLS sees it, as is any request naming the bookmarklet's parameters that did not come from the admin page itself. Otherwise any web page could make your signed-in browser create short links (to its own phishing pages, for example). Use the form on the admin page, or the API from scripts. Opening the admin page from a link elsewhere still works, and the API is not affected because scripts do not send browser headers.
H=$(sudo sed -n 's/^YOURLS_HOST=//p' /var/lib/cloudimg/yourls-credentials.txt)
C=(curl -s --cacert /etc/ssl/yourls/yourls.crt --resolve "$H:443:127.0.0.1")
{
"${C[@]}" -o /dev/null -w 'bookmarklet opened from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "https://$H/admin/index.php?u=https%3A%2F%2Fexample.org%2F"
"${C[@]}" -o /dev/null -w 'same, with an encoded parameter name: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "https://$H/admin/index.php?%75=https%3A%2F%2Fexample.org%2F"
"${C[@]}" -o /dev/null -w 'API called from a web page on another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "https://$H/yourls-api.php?action=version"
"${C[@]}" -o /dev/null -w 'admin page opened from a link elsewhere: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "https://$H/admin/index.php"
} | tee /dev/stderr | tr '\n' ' ' | grep -qx 'bookmarklet opened from another site: 403 same, with an encoded parameter name: 403 API called from a web page on another site: 403 admin page opened from a link elsewhere: 200 ' && echo "cross-site requests refused as expected"
bookmarklet opened from another site: 403
same, with an encoded parameter name: 403
API called from a web page on another site: 403
admin page opened from a link elsewhere: 200
cross-site requests refused as expected
Step 11: What the front door refuses
The API refuses a request with no credential, a wrong signature or a wrong password. Another web site cannot post to YOURLS from a browser, compressed request bodies are refused, YOURLS' configuration, its library folder and its web installer are never served, a PHP path with anything after .php (such as /yourls-api.php/x, which some front-controller setups hand to PHP as "path info") is refused before PHP, plain HTTP only redirects and unknown host names get no answer:
H=$(sudo sed -n 's/^YOURLS_HOST=//p' /var/lib/cloudimg/yourls-credentials.txt)
C=(curl -s --cacert /etc/ssl/yourls/yourls.crt --resolve "$H:443:127.0.0.1")
{
"${C[@]}" -o /dev/null -w 'API, no credential: %{http_code}\n' -d action=version "https://$H/yourls-api.php"
"${C[@]}" -o /dev/null -w 'API, wrong signature: %{http_code}\n' -d action=version -d signature=0123456789abcdef0123456789abcdef "https://$H/yourls-api.php"
"${C[@]}" -o /dev/null -w 'API, wrong password: %{http_code}\n' -d action=version -d username=admin -d password=not-the-password "https://$H/yourls-api.php"
"${C[@]}" -o /dev/null -w 'cross-site POST: %{http_code}\n' -H 'Origin: https://evil.example' -d x=1 "https://$H/admin/index.php"
"${C[@]}" -o /dev/null -w 'compressed body: %{http_code}\n' -H 'Content-Encoding: gzip' --data-binary x "https://$H/yourls-api.php"
"${C[@]}" -o /dev/null -w 'configuration file: %{http_code}\n' "https://$H/user/config.php"
"${C[@]}" -o /dev/null -w 'web installer: %{http_code}\n' "https://$H/admin/install.php"
"${C[@]}" -o /dev/null -w 'path after .php: %{http_code}\n' "https://$H/yourls-api.php/x"
curl -s -o /dev/null -w 'plain http: %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/admin/"
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve "other.example:443:127.0.0.1" https://other.example/admin/ || true
} | tee /dev/stderr | tr '\n' ' ' | grep -qx 'API, no credential: 403 API, wrong signature: 403 API, wrong password: 403 cross-site POST: 403 compressed body: 415 configuration file: 404 web installer: 404 path after .php: 404 plain http: 301 unknown host name: 000 ' && echo "all refused as expected"
API, no credential: 403
API, wrong signature: 403
API, wrong password: 403
cross-site POST: 403
compressed body: 415
configuration file: 404
web installer: 404
path after .php: 404
plain http: 301
unknown host name: 000
all refused as expected
Step 12: What is logged
nginx's access log, /var/log/nginx/access.log, records each request's client address, method, path and status, but never its query string, so a signature or password sent in an API URL is not written down. nginx normally also writes the full request line, query string included, to its error log when it refuses a request (a body over 1 MB gets 413, more than 64 open connections from one address get 429) or cannot reach PHP (502). On this image nginx's error log therefore records start-up problems only; those refusals still appear in the access log with their status. PHP's own errors go to /var/log/yourls/php-error.log, and php-fpm keeps no request log.
grep -h '^error_log\|^access_log' /etc/nginx/conf.d/cloudimg-yourls.conf
! sudo grep -rqaE 'signature=|password=' /var/log/nginx /var/log/yourls && echo "no API credentials in the web logs"
error_log /var/log/nginx/error.log emerg;
access_log /var/log/nginx/access.log cloudimg_yourls;
no API credentials in the web logs
To investigate a problem, raise the level for a while (error_log /var/log/nginx/error.log warn; in that file, then sudo systemctl reload nginx), and put it back afterwards. While it is raised, API URLs that carry a signature or password are written to the error log when a request fails, so use POST (Step 6) while you debug.
Step 13: The outbound fence
Each time you add a link, YOURLS downloads the target page to read its title, from the instance. Without a fence, anyone who can add links could use that to read the EC2 instance metadata service (which hands out the instance role's AWS credentials), reach services on the instance itself, or probe hosts inside your VPC. On this image the download goes to public addresses only, enforced twice:
- In YOURLS. YOURLS 1.10.5 and later can refuse to fetch titles from private, loopback and link-local addresses, checking every redirect, but by default only for visitors who are not signed in. This image switches that check on for everyone, including the admin and the API.
- In the kernel. YOURLS' check looks the name up and then fetches it, so a DNS name that changes its answer in between ("DNS rebinding") could slip past it. Below YOURLS, php-fpm's
IPAddressDenyand an nftables table for YOURLS' Linux user refuse connections to the metadata service, to the instance itself (except name lookups), to every private, shared and reserved address range and to your VPC's own IPv4 and IPv6 blocks, which first boot reads from the instance metadata. MariaDB has no network at all.
You can see the rules, then add links aimed at the metadata service, at this instance's SSH server and at a private address: none of them gets a title (YOURLS uses the URL itself as the title), while a public page does:
sudo nft list table inet cloudimg_yourls_egress | grep -oE 'comment "cloudimg-[a-z0-9]+"' | sort -u
H=$(sudo sed -n 's/^YOURLS_HOST=//p' /var/lib/cloudimg/yourls-credentials.txt)
C=(curl -s --cacert /etc/ssl/yourls/yourls.crt --resolve "$H:443:127.0.0.1")
umask 077; SF=$(mktemp)
sudo sed -n 's/^YOURLS_API_SIGNATURE=//p' /var/lib/cloudimg/yourls-credentials.txt | tr -d '\n' > "$SF"
add() { "${C[@]}" --data-urlencode "signature@$SF" -d format=json -d action=shorturl -d "keyword=$1" --data-urlencode "url=$2" "https://$H/yourls-api.php" \
| python3 -c 'import json, sys; d = json.load(sys.stdin); t = d.get("title", ""); u = sys.argv[1]; print(sys.argv[2] + ":", "page title fetched" if t and t != u else "no page fetched")' "$2" "$3"; }
S=$(date +%s)
{
add "fencemd$S" "http://169.254.169.254/latest/meta-data/?$S" "metadata service"
add "fencessh$S" "http://127.0.0.1:22/?$S" "this instance's SSH"
add "fencevpc$S" "http://10.0.0.2/?$S" "private address"
add "fencepub$S" "https://example.com/?fence=$S" "public page"
} | tee /dev/stderr | tr '\n' ' ' | grep -qx "metadata service: no page fetched this instance's SSH: no page fetched private address: no page fetched public page: page title fetched " \
&& sudo mariadb yourls -e "DELETE FROM yourls_url WHERE keyword LIKE 'fence%$S'" && echo "fenced"
RC=$?; rm -f "$SF"; [ "$RC" -eq 0 ]
comment "cloudimg-loopback"
comment "cloudimg-metadata"
comment "cloudimg-metadata6"
comment "cloudimg-private"
comment "cloudimg-private6"
comment "cloudimg-vpc"
comment "cloudimg-vpc6"
metadata service: no page fetched
this instance's SSH: no page fetched
private address: no page fetched
public page: page title fetched
fenced
The last command removes the four test links again. If your short links point at pages on your own private network, they still redirect normally: only the title download is fenced, and you can type the title yourself.
Step 14: Run the self-test
The image ships a self-test that checks the admin sign-in (five wrong passwords, then the right one), the session cookie, the API (no credential, a wrong signature and a wrong password refused, the real signature and password accepted), a short link round trip, the request policy, the listening ports and the certificate:
sudo yourls-selftest | tail -n 1 | tee /dev/stderr | grep -qx YOURLS_SELFTEST_OK
YOURLS_SELFTEST_OK
Step 15: Rotate the password and API signature
The helper generates a new admin password and a new cookie key, which also changes the API signature, puts them in force at once and writes them to the credentials file. It never prints them. Everyone signed in is signed out, and scripts need the new signature:
sudo yourls-rotate-credentials | tee /dev/stderr | grep -q 'are in force' \
&& sudo yourls-selftest | tail -n 1 | grep -qx YOURLS_SELFTEST_OK && echo "self-test passes with the new credentials"
yourls-rotate-credentials: a new admin password and API signature are in force; read them with: sudo cat /var/lib/cloudimg/yourls-credentials.txt
self-test passes with the new credentials
Step 16: Your own settings and plugins
/var/www/yourls/user/config.php is managed by the image's helpers (they rewrite it when you rotate credentials or change the site name). Put your own YOURLS settings in /var/www/yourls/user/config-local.php instead, which config.php loads and the helpers never touch. For example, YOURLS asks yourls.org for new releases from the admin page (sending your site's address); to switch that off:
sudo nano /var/www/yourls/user/config-local.php
<?php
define( 'YOURLS_NO_VERSION_CHECK', true );
Keep the file owned by root and readable by the yourls group (sudo chown root:yourls and sudo chmod 0640), then run sudo systemctl reload php8.3-fpm. The YOURLS files are owned by root and read-only to the web server, so a plugin cannot be uploaded through the browser: copy a plugin's folder into /var/www/yourls/user/plugins/ as root and activate it on the admin page's Manage Plugins screen. Plugins run with the same fence as YOURLS.
Step 17: Keep short links trustworthy
A shortener on your domain lends that domain's reputation to wherever its links point. Phishers look for open shorteners for exactly that reason, and mail filters and browsers block domains whose short links lead to malware. This image keeps link creation private (only admin, through the admin page or the API with the signature), so nobody else can create links on your domain. Keep it that way:
- Do not install plugins that let visitors create links, and do not set
YOURLS_PRIVATEtofalse. - Give the API signature only to scripts you control, and rotate it (Step 15) if it may have leaked.
- Check new links now and then on the admin page, and delete anything you did not create.
Step 18: Back up and restore
Everything YOURLS stores (links, click logs, options) is in the MariaDB database yourls. Back it up with:
sudo mariadb-dump --single-transaction yourls | gzip > yourls-$(date +%F).sql.gz
Restore it on a new instance of this image with gunzip -c yourls-<date>.sql.gz | sudo mariadb yourls, then copy across /var/www/yourls/user/config-local.php if you created one. Run sudo yourls-set-site on the new instance if the short links' domain is moving with them.
Step 19: Security notes and the build record
The certificate generated at first boot is a server certificate only: it cannot sign other certificates.
openssl x509 -in /etc/ssl/yourls/yourls.crt -noout -ext basicConstraints,keyUsage,extendedKeyUsage | tee /dev/stderr | grep -q 'CA:FALSE'
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
The YOURLS files are the 1.10.6 release archive, checked against its SHA-256 at build time, and no GeoIP database is on the image:
grep -oE "'YOURLS_VERSION', '[0-9.]+'" /var/www/yourls/includes/version.php
sed -n '1,3p' /usr/share/doc/cloudimg-yourls/BUILD-INFO.txt
echo "GeoIP databases on the image: $(sudo find / -xdev -name '*.mmdb' 2>/dev/null | wc -l)" | tee /dev/stderr | grep -qx 'GeoIP databases on the image: 0'
'YOURLS_VERSION', '1.10.6'
YOURLS 1.10.6 (DB version 507) by cloudimg
source https://github.com/YOURLS/YOURLS/archive/refs/tags/1.10.6.tar.gz
sha256 95549b8463c67a88eaa93f5dd8079599bc895f5ca047001ce385d1afdda26563
GeoIP databases on the image: 0
YOURLS is MIT licensed; its licence and MaxMind's GeoLite2 notices are in /usr/share/doc/cloudimg-yourls/. php-fpm runs YOURLS as its own unprivileged user yourls with no access to the credentials file or the TLS key, a read-only view of the system and of the YOURLS files, and only the capabilities php-fpm needs to start its workers. MariaDB and php8.3-fpm come from Ubuntu's universe archive (Ubuntu 24.04 ships them only there); everything else is from main, and the build checked that no Ubuntu Pro (ESM) security fix was outstanding for any of them.
Step 20: Keep it up to date
Ubuntu security updates, including PHP, MariaDB and nginx, install automatically. To apply everything now:
sudo apt-get update && sudo apt-get upgrade -y
New YOURLS releases arrive as new versions of the AWS Marketplace listing: launch the new version, restore your database backup into it (Step 18) and run sudo yourls-set-site with your domain. YOURLS upgrades the database the first time you sign in.
Support
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
The other versions on this listing are on the developer and API tools guide.