Self-hosted service
SinTan1729/chhoto-url avatar
SinTan1729/chhoto-url

chhoto-url calls itself complete and ships patches every few days

A simple, blazingly fast, selfhosted URL shortener with no unnecessary features; written in Rust.

977 stars96 forksRustMIT

At a glance

What is it?
chhoto-url is a self-hosted URL shortener written in Rust on Actix Web with a plain HTML frontend, whose whole pitch is a small image and a short feature list. The interesting parts are the ones the author writes as warnings: a random short code that starts colliding past a few thousand links, a database that can be mangled by an update, and a cargo audit in the Makefile whose result is discarded.
Who is it for?
chhoto-url fits a personal or small-team deployment where you want a shortener you can read, and it does not fit an organisation that needs accounts, audit trails or anything resembling multi-tenancy, since the author lists user management as bloat that will never be implemented and suggests running separate containers per person instead. Two operational notes carry more weight than the feature list.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Complete, not dead, and three patch releases in six days

The maintenance statement comes early and is unusual in how blunt it is. The author says not to worry if there is no activity for a long time, considers the project complete rather than dead, is unlikely to add new features, will try to fix every bug reported, and will try to keep it updated in terms of security vulnerabilities. Feature requests are still welcome through an issue template.

The release history sits awkwardly next to that. 7.8.0 landed on 2026-09-18, 7.8.1 on 2026-09-21 and 7.8.2 on 2026-09-23, and the last push to the default branch was 2026-09-27. Whatever those patch releases contain is not stated, but the pattern of a 7.x line moving every few days is not what an abandoned project looks like.

The project is MIT licensed, mirrored on GitHub and Codeberg, published to Docker Hub and the GitHub container registry, and served by the author at chhoto.link. The source carries SPDX headers naming Sayantan Santra and the years 2023 to 2026, including in the Makefile, and the repository has 977 stars, 96 forks and 3 open issues.

There is a list of things that will never be built

A refusal list is unusual enough to be useful documentation, and this project has one titled as bloat that will not be implemented. It rules out tracking or spying of any kind, noting that the only remaining logs are very simple ones mostly meant for debugging. It rules out user management, and the alternative offered is blunt: run separate containers for everyone, or use something else. It rules out cookies, newsletters, privacy popups and paywalls or messages begging for donations.

The privacy claim is kept narrow on purpose elsewhere too. Hit counting is described as privacy-respecting with the definition given inline: only the hit is recorded and nothing else. There is no referrer, no user agent, no timestamp attached to a hit in that description.

Custom notes attached to a link are a user-facing feature rather than analytics, and the interface can filter the list by short link, long link and notes. So the project collects nothing about its users and lets its users write whatever they want next to a link, which is a coherent line rather than a contradiction.

Two size claims for the same image, and a typo in one of them

The project leads on size, and it states the numbers twice with different results. Near the top, the scratch image is under 4.5 MB compressed, the Alpine one under 8.5 MB compressed, and memory use is under 10 MB under regular use.

Further down, in the feature list, the same claim becomes a roughly 6 MB Docker image and under 5 MB of RAM under normal use, with a typo in the second figure that survives into the published page.

Neither set can be right for the same configuration, and the difference is large enough to matter to anyone sizing a deployment. The two plausible explanations are that the numbers come from different builds or different moments, with the feature line predating the top section, and that they refer to different images, since 6 MB sits between the 4.5 MB scratch figure and the 8.5 MB Alpine figure.

The origin story explains why the numbers exist at all. The author liked the simply-shorten project but thought it missed some features, and objected to a simple app shipping a 200 MB Docker image, mostly a bundled Java runtime. The rewrite in Rust was the answer to that, and the numbers are the proof, which makes it worth measuring yourself rather than trusting either figure.

The Makefile's vulnerability audit cannot fail anything

The test target starts with a dependency audit and then deliberately discards its result:

bash
cargo audit --file backend/Cargo.lock || true

The trailing condition means a reported vulnerability prints a warning and the make invocation continues to the actual test run, which is a cargo test with the lockfile enforced and the commit hash injected as CARGO_GIT_COMMIT. That is a deliberate choice to make audits informational in a local target, and it is also the reason the author separately promises to keep the project updated for security vulnerabilities: nothing in the build will stop a vulnerable dependency from being merged.

The rest of the file is a Podman-based workflow rather than the Docker Compose setup the README mentions. The setup target adds the musl target and bootstraps the buildx builder, every build targets x86_64-unknown-linux-musl with the lockfile enforced, the release image is built from an Alpine container file with the build argument TARGETARCH set to amd64, and the file includes .env at the top, so a make invocation needs that file to exist. A merge target switches to main, merges dev, and switches back, which is the release flow in two lines.

The development container drops all capabilities and mounts your frontend

The local run target is more interesting than a typical dev compose file. It stops any existing container, then starts the debug image with every capability dropped, the port taken from the environment, an env file, a data directory mounted to the container's data path, and the repository's own frontend directory mounted over the application's base frontend directory.

Two consequences follow. Dropping all capabilities means the container cannot do the things containers usually get by default, which is the right default and worth copying. Overlaying ./frontend means edits to the static files appear without a rebuild, which is also the point of the target, and it means the image's bundled frontend is not what you are testing when you run it.

The container names are fixed rather than parameterised, which is why the stop and remove steps filter on that name first, and the run target follows the container with a log follow. For production the compose file shipped in the deploy directory is the documented path, and the README's features list confirms a container with a provided compose file.

Past a few thousand links, the random code starts failing

The notes at the end of the README are the operational manual, and the sharpest one is about the short code itself. If you intend to have more than a few thousand short links, you are told to use the UID slug style with a slug length of 16 or more. Otherwise, generating new links will start to fail after a while.

That is birthday-collision arithmetic expressed as advice. Short random codes drawn from a moderate alphabet will collide, and once the space fills up, generation fails rather than retrying silently. The fix is more entropy, not a database change, and the environment variables are named CHHOTO_SLUG_STYLE and CHHOTO_SLUG_LENGTH.

The other two notes are about what is allowed and how it is stored. For safety, only https, http, ftp and magnet are accepted as long links by default, with CHHOTO_EXTRA_PROTOCOLS to widen that, which keeps javascript: and data: URLs out of a service that renders redirects. The word list behind the random names is a modified version of the list used by Docker's own name generator, with the exact file linked.

WAL mode, Argon2, and a warning about databases after updates

Three recommendations sit together at the end, and they are the ones to apply before the first link rather than after an incident. Enabling WAL mode through CHHOTO_SQLITE_USE_WAL_MODE is described as highly recommended, which matches SQLite's own advice for a service taking concurrent writes, and the links are stored in an SQLite database configured to be ACID by default with tuning options left open.

For credentials, the recommendation is Argon2 for hashing the password and API key, selected through CHHOTO_HASH_ALGORITHM. The project accepts a hashed password and API key rather than only plaintext, and the API is described as JSON-RPC adjacent, with a QR code generator and a mobile-friendly interface with automatic dark mode around it.

Then the warning that decides how you deploy it: although it is unlikely, a database can be mangled after an update, and for mission-critical use you should keep regular versioned backups and pin a minor release tag such as 5.8. Combined with the public mode, where anyone can add links without authentication and deletion or listing needs the admin password, plus the option to disable the frontend entirely and to force expiry times, that is a service designed for one operator who knows where the backup is.

Editorial conclusion

chhoto-url fits a personal or small-team deployment where you want a shortener you can read, and it does not fit an organisation that needs accounts, audit trails or anything resembling multi-tenancy, since the author lists user management as bloat that will never be implemented and suggests running separate containers per person instead. Two operational notes carry more weight than the feature list. First, the short code generator: past a few thousand links you have to move to a longer UID, and until you do, new links start failing. Second, the database: the recommended configuration is WAL mode, the author warns an update can leave it mangled, and the advice for anything mission-critical is versioned backups plus pinning a minor release tag. Put a TLS-terminating reverse proxy in front of it too, since the built-in password is not encrypted in transit.

Frequently asked questions

What does chhoto-url record about the links it shortens?

Only the hit. The feature list describes hit counting as privacy respecting with the explicit note that only the hit is recorded and nothing else, and the refusal list rules out tracking or spying of any kind, leaving only simple logs meant for debugging.

Why does chhoto-url start failing to generate short links?

Random short codes collide once enough links exist. The documentation says that with more than a few thousand links you should switch to the UID slug style with a slug length of 16 or more, set through CHHOTO_SLUG_STYLE and CHHOTO_SLUG_LENGTH, or new links will begin failing.

Which link protocols does chhoto-url allow by default?

Only https, http, ftp and magnet, for safety. More protocols can be enabled through CHHOTO_EXTRA_PROTOCOLS, which keeps schemes such as javascript: and data: out of a service that issues redirects.

Is chhoto-url still being developed?

The author says the project is complete rather than dead, is unlikely to add new features, and will try to fix reported bugs and update for security vulnerabilities. Feature requests are still taken through an issue template, and the last push to the default branch was 2026-09-27.

How should a chhoto-url database be protected?

Enable WAL mode, hash the password and API key with Argon2, and take regular versioned backups while pinning a minor release tag such as 5.8, because the documentation warns a database can be mangled after an update. A TLS-terminating reverse proxy such as Caddy is recommended since the built-in password is not encrypted in transport.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. SinTan1729/chhoto-url on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sintan1729-chhoto-url.svg)](https://hysenlabs.com/projects/sintan1729-chhoto-url)