Chhoto URL 7.10.8 on AWS User Guide
Overview
This guide covers launching and using the Chhoto URL 7.10.8 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg documents, forms and scheduling listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Chhoto URL is a deliberately small, self hosted URL shortener written in Rust. It turns a long link into a short one, either with a readable generated slug (an adjective and a noun) or a word you choose. Links can carry a note, count their hits and expire on their own. A responsive web page covers day to day use, and a JSON API with an API key lets scripts and other tools create, list and delete links. Everything stays on your own instance in your own VPC.
Upstream Chhoto URL listens on plain HTTP and expects you to choose and set the password and API key yourself. The cloudimg image changes that:
- HTTPS from the first boot. nginx serves Chhoto URL on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. Chhoto URL itself listens on
127.0.0.1:4567only. - A password and an API key unique to your instance. First boot generates a 32 character password and a 64 character API key and writes them to
/root/chhoto-url-credentials.txt(root only). Chhoto URL's own configuration keeps only their argon2id hashes, so the plaintext never sits in the service's environment. - Fail closed. Chhoto URL refuses to start if its configuration file is missing, has the wrong permissions, is malformed, or still holds the image's placeholders, and it never falls back to running without a password.
- Hardened. Requests for host names the instance does not serve are dropped, the client address passed to Chhoto URL is always the real one (a forged
X-Forwarded-Foris overwritten), sign-in attempts are rate limited, onlyhttp,https,ftpandmagnetlinks are accepted, Chhoto URL runs as its own user in a systemd sandbox that can reach only loopback and the public internet (not the instance metadata service and not your VPC's private addresses), the AMI requires IMDSv2, and nginx is blocked from the metadata service too. - Built from the tagged source. Chhoto URL 7.10.8 (which includes the fix for advisory GHSA-gvcx-7p9v-2462) is compiled from the upstream
7.10.8tag with its locked dependency list, and every shipped file is listed with its SHA-256 checksum in/usr/share/doc/chhoto-url/SHA256SUMS. The image is fully patched Ubuntu 24.04 LTS with unattended security upgrades on.
What is on the image: Chhoto URL 7.10.8 (/opt/chhoto-url/, licence, source details and crate list in /usr/share/doc/chhoto-url/), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
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 with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (Chhoto URL) and TCP 80 (redirects to HTTPS). Open 22 from your own networks only; open 443 and 80 to the people who will follow your short links.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Chhoto URL 7.10.8 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Chhoto URL is light, so a smaller type also works for a small team), your key pair and a security group as above.
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=chhoto-url}]'
First boot takes under a minute: it generates the password, the API key, the session signing key and the TLS certificate, then starts Chhoto URL and nginx.
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Chhoto URL 7.10.8 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
systemctl is-active chhoto-url-firstboot chhoto-url-egress chhoto-url nginx
sudo ss -tlnp | awk 'NR==1 || /:(22|80|443|4567|1729) /'
active
active
active
active
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 1024 127.0.0.1:1729 0.0.0.0:* users:(("chhoto-url",pid=10245,fd=15))
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=898,fd=3),("systemd",pid=1,fd=182))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=8359,fd=5),("nginx",pid=8358,fd=5),("nginx",pid=8357,fd=5))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=8359,fd=7),("nginx",pid=8358,fd=7),("nginx",pid=8357,fd=7))
LISTEN 0 1024 127.0.0.1:4567 0.0.0.0:* users:(("chhoto-url",pid=10245,fd=14))
LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=898,fd=4),("systemd",pid=1,fd=183))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=8359,fd=6),("nginx",pid=8358,fd=6),("nginx",pid=8357,fd=6))
LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=8359,fd=8),("nginx",pid=8358,fd=8),("nginx",pid=8357,fd=8))
chhoto-url-firstboot ran once on first boot. chhoto-url-egress applies the network backstop at every boot (it denies Chhoto URL your VPC's own address ranges, read from the instance metadata). Only nginx (80 and 443) and SSH listen on the network; Chhoto URL (4567) and its health endpoint (1729) are on loopback only.
Step 4: Get your URL, password and API key
The credentials file holds the address to open, the password and the API key. To print it:
sudo cat /root/chhoto-url-credentials.txt
Here is the same file with the two secret values left out:
sudo sed -E 's/^(chhoto\.(password|apikey))=.*/\1 (value hidden here)/' /root/chhoto-url-credentials.txt
# Chhoto URL 7.10.8 - generated on first boot by chhoto-url-firstboot.service. The password and the API key are
# unique to this instance; neither was baked into the image. Keep this file secret (root only, 0600).
CHHOTO_URL=https://ec2-34-238-85-184.compute-1.amazonaws.com/
chhoto.password (value hidden here)
chhoto.apikey (value hidden here)
#
# Sign in at CHHOTO_URL with the password alone (Chhoto URL has no user names). The certificate is self-signed until
# you install your own: /etc/ssl/chhoto-url/chhoto-url.crt
# API (the key goes in the X-API-Key header):
# curl --cacert /etc/ssl/chhoto-url/chhoto-url.crt -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
# -d '{"shortlink":"docs","longlink":"https://example.com/"}' https://ec2-34-238-85-184.compute-1.amazonaws.com/api/new
# New secrets: sudo chhoto-url-rotate-secrets --all Your own password: sudo chhoto-url-rotate-secrets --password-stdin
# Served name: sudo chhoto-url-set-hostname <your-domain> Self-test: sudo chhoto-url-selftest
# User guide: https://www.cloudimg.co.uk/guides/chhoto-url-7-10-8-on-ubuntu-24-04-aws/
The certificate is self signed, so your browser shows a warning the first time; Step 9 covers using your own DNS name and certificate. Chhoto URL has no user names: the password alone signs you in.
Step 5: Sign in
Open the CHHOTO_URL address in a browser, accept the certificate warning, enter the password from Step 4 and select Login. Tick Remember me to keep the session on that browser.

After signing in you see the shortener form above your Active links table, with each link's hit count, expiry and actions (copy, QR code, details, edit, delete).

Step 6: Create a short link
Paste a long URL into Long URL. Leave Short URL empty for a generated adjective-noun slug, or type your own word (lower case letters, digits, - and _). More options adds an expiry and a free text note, which the filter box searches.

Select Shorten!. The short link is copied to your clipboard and appears at the top of the table.

Step 7: Create links from the API
The API key from Step 4 goes in the X-API-Key header. These commands run on the instance and talk to nginx on loopback with the instance's own certificate; from another machine, use https://<your-name>/ directly.
KEY="$(sudo sed -n 's/^chhoto\.apikey=//p' /root/chhoto-url-credentials.txt)"
HOST="$(sudo sed -n 's#^CHHOTO_URL=https://\([^/]*\)/$#\1#p' /root/chhoto-url-credentials.txt)"
API=(curl -s -m 20 --cacert /etc/ssl/chhoto-url/chhoto-url.crt --resolve "$HOST:443:127.0.0.1")
echo "Create a short link:"
"${API[@]}" -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
--data-binary '{"shortlink":"ec2-docs","longlink":"https://docs.aws.amazon.com/ec2/","notes":"EC2 documentation"}' \
"https://$HOST/api/new"; echo
echo "Follow it:"
"${API[@]}" -o /dev/null -w 'GET /ec2-docs -> HTTP %{http_code}\n' "https://$HOST/ec2-docs"
echo "List every link:"
"${API[@]}" -H "X-API-Key: $KEY" "https://$HOST/api/all" \
| python3 -c 'import json, sys; [print(" %-16s -> %s hits=%s" % (l["shortlink"], l["longlink"], l["hits"])) for l in json.load(sys.stdin)]'
echo "Delete it:"
"${API[@]}" -o /dev/null -w 'DELETE /api/del/ec2-docs -> HTTP %{http_code}\n' -X DELETE -H "X-API-Key: $KEY" "https://$HOST/api/del/ec2-docs"
Create a short link:
{"success":true,"error":false,"shorturl":"https://ec2-34-238-85-184.compute-1.amazonaws.com/ec2-docs","enabled":true,"expiry_time":0}
Follow it:
GET /ec2-docs -> HTTP 308
List every link:
ec2-docs -> https://docs.aws.amazon.com/ec2/ hits=0
Delete it:
DELETE /api/del/ec2-docs -> HTTP 200
Leave out shortlink to have a slug generated for you; expiry_delay (seconds) is optional. The built in self test runs the same round trip (sign in, session, API key, redirect, delete) and prints no secret:
sudo chhoto-url-selftest
SELFTEST OK Chhoto URL 7.10.8 at https://ec2-34-238-85-184.compute-1.amazonaws.com/ (sign-in, session, API key, redirect, delete)
Step 8: Check the security posture from the command line
HOST="$(sudo sed -n 's#^CHHOTO_URL=https://\([^/]*\)/$#\1#p' /root/chhoto-url-credentials.txt)"
CA=/etc/ssl/chhoto-url/chhoto-url.crt
curl -s -m 10 -o /dev/null -w "http://$HOST/ -> %{http_code} (redirects to https)\n" --resolve "$HOST:80:127.0.0.1" "http://$HOST/"
curl -s -m 10 -o /dev/null -w 'https:// (the served name) -> %{http_code}\n' --cacert $CA --resolve "$HOST:443:127.0.0.1" "https://$HOST/"
curl -sk -m 10 -o /dev/null -w 'https:// (any other host name) -> %{http_code} (connection closed)\n' --resolve other.example:443:127.0.0.1 https://other.example/
curl -s -m 10 -o /dev/null -w 'POST /api/new without the API key -> %{http_code}\n' --cacert $CA --resolve "$HOST:443:127.0.0.1" \
-X POST -H 'Content-Type: application/json' --data-binary '{"longlink":"https://example.com/"}' "https://$HOST/api/new"
curl -s -m 10 -o /dev/null -w 'GET /api/all without a session -> %{http_code}\n' --cacert $CA --resolve "$HOST:443:127.0.0.1" "https://$HOST/api/all"
http://ec2-34-238-85-184.compute-1.amazonaws.com/ -> 301 (redirects to https)
https:// (the served name) -> 200
https:// (any other host name) -> 000 (connection closed)
POST /api/new without the API key -> 401
GET /api/all without a session -> 401
The service sandbox, the stored hashes and the file permissions:
systemctl show chhoto-url -p User -p NoNewPrivileges -p ProtectSystem -p IPAddressAllow
sudo stat -c '%a %U:%G %n' /etc/chhoto-url/chhoto-url.env /root/chhoto-url-credentials.txt
sudo sed -nE 's/^(CHHOTO_PASSWORD|CHHOTO_API_KEY)=[$](argon2id)[$]v=19[$](m=[0-9]+,t=[0-9]+,p=[0-9]+)[$].*/\1 holds an \2 hash (\3)/p' \
/etc/chhoto-url/chhoto-url.env
IPAddressAllow=::1/128 127.0.0.0/8
User=chhoto
ProtectSystem=strict
NoNewPrivileges=yes
640 root:chhoto /etc/chhoto-url/chhoto-url.env
600 root:root /root/chhoto-url-credentials.txt
CHHOTO_PASSWORD holds an argon2id hash (m=32768,t=3,p=1)
CHHOTO_API_KEY holds an argon2id hash (m=32768,t=3,p=1)
IPAddressAllow lets Chhoto URL reach loopback; everything else goes through the deny list, which covers the instance metadata service and every private and reserved range (including your VPC's own blocks, added at each boot), so Chhoto URL can reach only the public internet.
Step 9: Your own DNS name and certificate
Point a DNS record at the instance, then tell the instance to serve that name. This generates a new self signed certificate for it, sets the address Chhoto URL uses in the short links it returns, and redirects requests for the instance's IP addresses to the name:
sudo chhoto-url-set-hostname links.example.com
After setting the name, to use a certificate for it that your browsers already trust (for example from your own CA or from Let's Encrypt), install it over the instance's files, keeping the same names and permissions, and reload nginx:
sudo install -m 0644 -o root -g root your-fullchain.pem /etc/ssl/chhoto-url/chhoto-url.crt
sudo install -m 0600 -o root -g root your-private-key.pem /etc/ssl/chhoto-url/chhoto-url.key
sudo nginx -t && sudo systemctl reload nginx
Step 10: Change the password or API key
chhoto-url-rotate-secrets generates new values, stores their argon2id hashes, updates the credentials file, restarts Chhoto URL and checks that it came back. If anything fails, the previous values stay in force, so you cannot lock yourself out.
sudo chhoto-url-rotate-secrets --all # new password, API key and session key (signs everyone out)
sudo chhoto-url-rotate-secrets --api-key # new API key only
printf '%s' 'your-own-password' | sudo chhoto-url-rotate-secrets --password-stdin # 12-127 characters, no spaces
Step 11: Data, backups and settings
All links live in one SQLite database in /var/lib/chhoto-url/, owned by the chhoto user. Chhoto URL also keeps its own rolling backups of that database in /var/lib/chhoto-url/backups/.
sudo ls -l /var/lib/chhoto-url/
total 216
drwx------ 2 chhoto chhoto 100 Nov 9 10:37 backups
-rw------- 1 chhoto chhoto 53248 Nov 9 10:37 urls.sqlite
-rw------- 1 chhoto chhoto 32768 Nov 9 10:37 urls.sqlite-shm
-rw------- 1 chhoto chhoto 131872 Nov 9 10:37 urls.sqlite-wal
For a copy you can take off the instance, use SQLite's online backup, which is safe while Chhoto URL runs (Python's built in sqlite3 module does it, so nothing extra needs installing):
sudo install -d -m 0700 /var/backups/chhoto-url
sudo python3 -c 'import sqlite3; s = sqlite3.connect("/var/lib/chhoto-url/urls.sqlite"); d = sqlite3.connect("/var/backups/chhoto-url/urls-backup.sqlite"); s.backup(d); d.close()'
To restore, stop Chhoto URL (sudo systemctl stop chhoto-url), copy the backup over /var/lib/chhoto-url/urls.sqlite, set its owner back to chhoto:chhoto and mode 0600, and start Chhoto URL again. An EBS snapshot of the volume is the simplest whole instance backup.
Settings live in /etc/chhoto-url/chhoto-url.env (mode 0640, owner root:chhoto; Chhoto URL refuses to start if that changes). Useful optional settings from the upstream documentation include CHHOTO_SLUG_STYLE (Pair or UID), CHHOTO_SLUG_LENGTH, CHHOTO_REDIRECT_METHOD (TEMPORARY for temporary redirects) and CHHOTO_FRONTEND_PAGE_SIZE. Edit the file as root, then apply the change:
sudo systemctl restart chhoto-url
Step 12: Security recommendations
- Allow port 22 in the security group from your own networks only.
- Keep the credentials file private; rotate the password and API key if they may have leaked (Step 10), and give scripts the API key rather than the password.
- Use your own DNS name and a certificate your clients trust (Step 9).
- Short links redirect anyone who has them, so do not shorten URLs that carry secrets such as access tokens.
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Chhoto URL is open source under the MIT License; there is no per user or per CPU licence fee. The licence, the exact upstream commit and the list of the Rust crates compiled into the binary (with their licences) are in /usr/share/doc/chhoto-url/. cloudimg is not affiliated with or endorsed by the Chhoto URL project. The AMI price covers cloudimg's build, hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA