22 min read

Set Up Chhoto URL and Share Links with Localtonet

Install and verify Chhoto URL locally, then use an HTTP tunnel with Localtonet to share short links remotely, with security checks before exposure.

Self-Hosting Tutorials · Chhoto URL · Localtonet · 2026

Keep your short-link database local and make verified redirects reachable through a public HTTPS address

Chhoto URL is a lightweight, self-hosted URL shortener with a Rust backend, SQLite storage, and a browser-based management interface. With Localtonet, you can expose its working HTTP service without inbound router port forwarding or a public IP address. This guide explains deployment checks, redirect testing, authentication boundaries, persistent storage, backups, and tunnel operation. It also distinguishes application configuration from network reachability so that you can diagnose failures without weakening security.

🔒 Separate public redirects from privileged management 🌐 Publish a locally verified HTTP endpoint 💾 Test container replacement and database recovery

Understand the short-link request path

Chhoto URL stores an association between a short alias and a destination URL. When someone requests that alias, the application looks up the record and returns a redirect. The browser then contacts the destination itself. Localtonet carries the request to your locally hosted shortener; it does not become the host for the destination website.

A remote reader reaches locally hosted Chhoto URL through Localtonet and follows a redirect to the destination website.
The shortener must remain reachable to resolve an alias. After receiving the redirect, the reader connects to the destination separately.

Three different addresses matter: the local HTTP endpoint reached by our client, the public origin recipients use, and the destination stored in each record. For example, an otherwise valid short link can contain an inaccessible LAN hostname. Conversely, a public short link can reach the correct application but redirect to the wrong destination because its saved record is incorrect. These require different fixes.

🔗 Editable links Create generated slugs or custom aliases, change destinations, temporarily disable records, and configure expiry.
🗃️ SQLite storage Preserve the database outside the container writable layer and back it up independently of container images.
📊 Simple hit counting The project records link-use counts rather than offering visitor tracking or a marketing analytics platform.
🔑 Management options Password authentication, OIDC, API-key operation, notes, filtering, and QR codes support different management workflows.

The backend uses asynchronous Rust and Actix Web; the frontend uses HTML, CSS, and vanilla JavaScript. The project deliberately avoids per-user management. For independent accounts, workspaces, or organizational permissions, evaluate separate instances or a different application rather than assuming that a shared administrator password provides those boundaries.

Public mode is a separate decision from public reachability. Chhoto URL can allow anonymous link creation while requiring administrative access to list or delete links. Most private shorteners need anonymous redirect access, not anonymous creation. Keep that distinction explicit throughout deployment and testing.

Prepare a release-consistent deployment

Deployment verification boundary

The available project material confirms the container workflow but does not include the installation guide or Compose definition itself. Consequently, an exact release-specific image reference, configuration file, database path, application port, and accepted configuration values cannot be verified here. The operational commands below apply to a reviewed Docker Compose deployment, not a supplied ready-to-run Chhoto URL installation. Obtain the installation and configuration resources linked from the official Chhoto URL website or project repository before proceeding. Do not substitute guessed values.

Choose one release and obtain its installation instructions, configuration reference, and deployment definition together. Repository development can be ahead of released container images. A version string in a source file is not proof that a matching image tag exists, and a historical release page is not proof of the latest supported version.

The project describes Docker, Podman, and other OCI-compatible workflows. The command examples here use Docker Engine with the Compose plugin on a host with a POSIX shell, curl, and, for database checks, the SQLite command-line tool. Podman users should use their verified runtime equivalents rather than assuming every Compose option behaves identically.

docker version
docker compose version
curl --version
sqlite3 --version

Confirm that Docker can reach its daemon with your intended administrative account. Docker access is privileged; do not solve permission errors by making its socket world-writable. Provision durable storage, sufficient free disk space, a protected backup location, and a device on which the Localtonet client can run continuously.

Deployment valueWhat to establishWhy it matters
Image referenceOfficial registry, available release tag, architecture, and preferably recorded digestMakes deployment and rollback reproducible
ConfigurationRelease-specific names, formats, defaults, and authentication modeA running process does not prove settings were accepted
Database storageConfigured database file and the mount containing itProtects records during container replacement
ListenerContainer listener, published host port, and host bindingDetermines what our client can reach
Public originIntended HTTPS hostname used in generated short linksPrevents distribution of LAN-only links
Recovery materialDatabase backup, matching image, deployment files, and protected secretsAllows reconstruction rather than just file recovery

Review configuration, storage, and startup

Select the image and authentication settings

Inspect the official deployment definition before running it. Replace demonstration credentials, choose an intentional image version, and review every published port and mounted directory. For important deployments, avoid unattended movement to a new release. The project recommends versioned backups and a deliberate minor-release policy; pinning an exact image digest additionally makes the selected artifact reproducible.

Use a unique administrative secret. The project supports hashed passwords and API keys and recommends Argon2, but the accepted hash representation and configuration syntax must match your release. Do not assume support for Docker secrets, a file-based secret option, or an environment-variable suffix without checking.

Keep secret-bearing files outside version control and restrict their filesystem permissions. Environment variables can be visible to privileged container administrators and diagnostic tools, so a local environment file is not a complete secret-management boundary. Avoid publishing full container inspection output, rendered Compose configuration, request headers, or logs containing credentials.

Map the actual database location

Identify the database path the application will actually open. Verify that its parent storage is a bind mount or persistent named volume, not just a directory inside the image. SQLite may create journal or WAL-related files beside the database, so a durable database-directory mapping is generally easier to operate safely than a single-file mapping. Follow the project definition where it specifies the storage layout.

Check write permissions using the container’s actual runtime user. Do not make database storage universally writable to work around a user-ID mismatch. If an existing instance contains records, make a consistent backup before changing mounts: an empty mount can hide existing files and make a healthy database appear to have vanished.

Bind only the necessary network path

For a Localtonet client running directly on the same host, a host port published on loopback can limit unnecessary LAN exposure. The application listener inside the container must still be reachable through the container network used by port publication. Host loopback binding and container loopback binding are not interchangeable.

If our client runs on another device, it needs a permitted LAN path to the published host address. If the client itself runs in a container, its loopback address normally identifies that container, not the host or the Chhoto URL container. Choose the target according to the client’s network environment and test from there.

Validate and start a reviewed Compose project

Run the following from the directory containing your reviewed Compose definition. For a non-default filename, consistently supply Docker Compose’s -f option. These are ordinary Docker commands, not Chhoto URL-specific installation commands.

docker compose config --quiet
docker compose config --services
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100

Validation checks Compose syntax, not whether Chhoto URL accepts the application settings. Inspect startup logs for invalid configuration, database errors, unexpected defaults, or repeated restarts. Record the actual image and selected service name. The examples below use SERVICE as a shell variable that you set to that name:

read -r -p "Compose service name: " SERVICE
docker compose ps "$SERVICE"
docker compose logs --tail=100 "$SERVICE"

CID="$(docker compose ps -q "$SERVICE")"
docker inspect --format '{{json .Mounts}}' "$CID"
docker inspect --format '{{json .NetworkSettings.Ports}}' "$CID"

Keep inspection output private. Compare the effective mounts and port publication with the deployment file, rather than assuming that the file you edited is the one currently running.

Review database and slug settings

The project strongly recommends WAL mode. Enable it through the release’s documented configuration instead of changing a live database’s journal mode experimentally. WAL also affects backup handling, discussed below.

The current repository guidance identifies CHHOTO_SLUG_STYLE and CHHOTO_SLUG_LENGTH. It recommends the UID style and a length of at least 16 for instances expecting more than a few thousand links, to reduce namespace exhaustion. The supplied material does not establish the exact accepted style literal, its case, other accepted values, or backward compatibility with every release. Confirm those before setting the variables; the descriptive word “UID” alone is not sufficient evidence for an executable configuration.

Custom aliases should have an ownership policy, especially for names resembling support, login, or administrative routes. For an intentionally public shortener, consider the project’s forced-expiry capability alongside moderation and abuse handling.

Verify locally before creating a tunnel

Host-browser and LAN-browser checks use different addresses to reach the same Chhoto URL service.
Test from the application host first, then from the device that will run the Localtonet client when it is a different machine.

Open the published local endpoint and authenticate. Create a disposable record pointing to a harmless HTTPS destination you control. Record the alias and exact destination, then check that the record appears in the interface. Test editing, disabling, re-enabling, and expiry using disposable links before depending on those behaviors operationally.

Inspect the redirect without following it

Copy the test short URL and inspect a normal GET response. Do not use only a HEAD request, because an application may handle it differently. The following command does not follow redirects:

read -r -p "Test short URL: " SHORT_URL
curl --silent --show-error --dump-header - --output /dev/null "$SHORT_URL"

For an active link, inspect the redirect status and Location header. Compare the destination with the saved record. Only then follow the link in a browser. A browser can cache a redirect, so use a fresh session or repeat the command when testing edited destinations. Tests can increment hit counts; do not expect production counts to exclude monitoring traffic.

Test anonymous access separately

Use a private browser session with no saved application credentials. A recipient should be able to follow an intended public short link. In a separate test, attempt a disposable management action without authentication. With public creation disabled, unauthorized creation, editing, deletion, and listing should not succeed. Verify the database state afterward, not merely whether a button was hidden.

A login page returning HTTP 200 is not evidence of unauthorized management. Conversely, hiding the management interface does not secure an unprotected API. Test the release’s documented management requests without a password or API key, using only disposable records. Do not guess API routes or replay a destructive request against real data.

Prove persistence through replacement

A restart is not a persistence test

An ordinary container restart preserves that container’s writable layer. Records surviving a restart do not prove that storage will survive replacement. Back up first, then deliberately recreate the container while retaining the same verified persistent storage.

After making the backup described below, record the container ID, recreate without pulling a new image, and compare IDs:

OLD_CID="$(docker compose ps -q "$SERVICE")"
docker compose up -d --no-deps --force-recreate --pull never "$SERVICE"
NEW_CID="$(docker compose ps -q "$SERVICE")"
printf 'Before: %s\nAfter:  %s\n' "$OLD_CID" "$NEW_CID"
docker compose logs --tail=100 "$SERVICE"

The IDs should differ, while the existing test record, destination, and settings should remain available. Inspect the new container’s mounts too. Do not use docker compose down -v, remove the persistent volume, or renew anonymous volumes during this test. Named storage with an explicit mapping is preferable to storage whose lifecycle you have not established.

Connect the verified HTTP service to Localtonet

Our client establishes an outbound connection to a Localtonet relay and exposes the selected local service through a public address. This does not require inbound router port forwarding, a public IP address, or VPN setup. It does require permitted outbound connectivity and a local target reachable from the client device.

The exact current HTTP documentation sequence was not included in the available material. The checklist below therefore describes our supported general tunnel lifecycle, not a verified reproduction of the HTTP page’s screen-by-screen instructions. Use the Localtonet HTTP tunnel documentation for its current fields and order.

1

Install and run our client

Run the Localtonet client on a device that can reach the HTTP endpoint you tested. Keep it connected while public links need to resolve.

2

Authenticate the selected device

Use its device-specific authentication token through the supported client workflow. Keep that token private.

3

Select an available relay

Choose a server or region available in the current dashboard. Do not reuse a guessed server code.

4

Configure the HTTP target

Use the verified local IP address and published port. HTTP process types include Random Sub Domain, Custom Sub Domain, and Custom Domain, subject to current availability and requirements.

5

Start the tunnel

Press Start and use the assigned public HTTPS address. Saving a configuration alone does not start a tunnel.

6

Stop or delete it when no longer required

Stopping ends public access through that tunnel. For enduring short links, coordinate downtime instead of stopping the tunnel after initial testing.

Apply the public site URL

Once the public address is known, update Chhoto URL’s documented site URL setting to that origin. Do not confuse it with the local listener address or the destination of an individual link. For a Compose environment change, docker compose restart does not apply a changed container environment; reconcile the updated deployment instead:

docker compose config --quiet
docker compose up -d --no-deps "$SERVICE"
docker compose logs --tail=100 "$SERVICE"

Create a fresh test link and check its exact scheme and hostname. Changing the site URL does not rewrite old URLs already printed in QR codes or copied into messages. Choose an address suitable for the lifetime of the links, and check current custom-domain requirements before committing to a public hostname.

Test from outside the LAN

Use a mobile connection or another external network. Check public HTTPS without a certificate warning, anonymous redirect access, and denial of unauthorized management separately. Repeat the header inspection against the public short URL and compare its Location with the local result. Do not enter credentials through a certificate warning or an unintended plaintext public URL.

Keep transport and authorization boundaries explicit

Exposure review distinguishes short-link redirects, link management, stored data, and secrets.
Public connectivity and application authorization are separate controls. Test both before sharing production links.

Chhoto URL’s basic password authentication does not itself encrypt transport. The project recommends an SSL-capable reverse proxy. With Localtonet HTTP tunnels, the public address uses HTTPS, but the local hop follows the target you configured. Keep a plaintext local hop on the same trusted host or a controlled network rather than assuming public HTTPS protects every separate connection.

Leave public creation disabled unless it is an intentional service requirement. A shortener’s hostname carries reputational risk because recipients cannot see the final destination directly in an alias. Decide who may create links, how abusive destinations are reported, and when records are disabled. Keep application destination-scheme restrictions in place.

If you use OIDC, validate issuer, callback address, claims, and account authorization against the selected release. Authentication through an identity provider is not automatically authorization for every identity it recognizes. For API clients, protect keys independently of browser credentials and verify that anonymous requests cannot perform privileged operations.

Never share device tokens, password hashes, database backups, or sensitive destination lists in support posts. A hash is still security-sensitive material. Stop the tunnel while investigating unexpected administrative access or persistence failures.

Back up, restore, upgrade, and recover

Create a consistent SQLite backup

For a straightforward maintenance backup, stop all writers and copy the complete database storage directory. Stop the public tunnel first to avoid serving maintenance errors. Then stop the Compose service cleanly and verify it is stopped. A stopped copy must include any journal or WAL files present, not just the main database file.

The following procedure assumes you have positively identified a host-accessible directory containing the complete database storage. For a named volume, obtain that directory through an appropriate administrator-controlled volume backup workflow. Do not guess Docker’s internal storage path. Store the backup outside the source directory.

docker compose stop "$SERVICE"
docker compose ps -a "$SERVICE"

read -r -p "Absolute database storage directory: " DATA_DIR
read -r -p "Absolute backup directory outside database storage: " BACKUP_ROOT
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
umask 077
mkdir -p "$BACKUP_ROOT/$STAMP"
cp -a "$DATA_DIR/." "$BACKUP_ROOT/$STAMP/"

Confirm the copy succeeded and contains the expected files before restarting. Use an account with the required read permissions; preserve ownership metadata needed for restoration. Save the matching deployment files and image reference separately, with secrets protected. Copy backups to another failure domain rather than leaving the only copy on the application disk.

docker compose start "$SERVICE"
docker compose logs --tail=100 "$SERVICE"

Verify locally before restarting the tunnel. If downtime is unacceptable, SQLite’s online backup mechanism is an alternative to an uncontrolled live file copy, but coordinate it with your deployment and test restoration. Copying only the main database while WAL writes are active can omit committed transactions.

Restore into an isolated deployment

Create a separate recovery directory and copy the backup there. Keep the original backup unchanged. With the SQLite tool installed, check the copied database using its actual filename:

read -r -p "Absolute copied SQLite database file: " RESTORED_DB
sqlite3 "$RESTORED_DB" 'PRAGMA integrity_check;'

An ok result checks structural integrity, not whether every business record is correct. Inspect several known aliases and destinations through a test application too.

  1. Copy the reviewed Compose definition into a separate recovery working directory.
  2. Use the image matching the backup and map only the copied database storage.
  3. Publish a different, unused host port on loopback. Do not create a tunnel to the recovery instance.
  4. Remove conflicting explicit container names and production-specific external volume references.
  5. Review mounts carefully. A different Compose project name alone does not isolate absolute bind mounts or explicitly shared volumes.

From that reviewed recovery directory, use a distinct project name:

docker compose -p chhoto-restore config --quiet
docker compose -p chhoto-restore up -d
docker compose -p chhoto-restore ps
docker compose -p chhoto-restore logs --tail=100

Verify login, known records, redirect headers, disabled links, and expiry. Generated links may still contain the production origin until the recovery instance’s site URL is changed. For isolation, request the test listener directly with a known alias instead of blindly following production links displayed in the interface. Finish with docker compose -p chhoto-restore down; retain the verified backup.

Upgrade with a rollback boundary

Record the current image digest and deployment configuration, take a fresh backup, and read intervening release notes. Test the target image against a restored database copy first. Check creation, editing, redirects, expiry, authentication, and API integrations before upgrading production.

Do not assume an older image can read a database modified by a newer release. A safe rollback may require the old image, old configuration, and pre-upgrade database together. Restoring that backup also discards writes made after it, so define a maintenance window and recovery decision point. Preserve the failed upgraded database for investigation rather than overwriting your only copy.

Monitor the complete dependency chain

Public redirects depend on the application, storage, local target path, client connection, and running tunnel. After a reboot, inspect those dependencies in that order. Container restart policy does not prove that the Localtonet client or tunnel is active. A successful homepage check also does not prove that database-backed redirects work.

Diagnose the symptom, not just the hostname

SymptomFirst checkCorrective direction
Container exits or restarts repeatedlyStatus and startup logsResolve rejected settings, database permissions, or image compatibility before tunneling
No local pagePublished port and listener bindingDistinguish container listener from host publication; check for port conflicts
Local page works, public address failsClient connection, selected device, Start state, target addressTest the target from the actual client device or container
Generated short URL contains a private hostnameSite URL settingApply the public origin and recreate the container if environment changed
Correct short URL redirects to an unexpected destinationSaved destination and response Location headerFix the record, distinguish cached redirects, and inspect any later destination-side redirect
Links disappear after container replacementEffective database path and mountsLocate the previous storage and recover before creating new production records
Writes fail but reads workDisk capacity, storage permissions, database logsRestore writable storage safely; do not delete WAL files as a quick fix
Slug generation fails as the instance growsRelease-specific slug configurationReview CHHOTO_SLUG_STYLE and CHHOTO_SLUG_LENGTH against the project’s UID guidance

Records disappearing after an ordinary restart warrant investigation too, but do not by themselves prove ephemeral storage: the same container’s writable layer normally survives. Check whether the operation actually recreated the container, whether a different Compose project started, or whether configuration selected a different database.

When a redirect appears wrong, inspect the first response without following it. If its Location matches the saved destination, a later redirect may originate at the destination website. Changing the shortener’s site URL will not repair that downstream behavior.

Frequently asked questions

Does Localtonet install Chhoto URL?

No. Install and verify the application first. Our HTTP tunnel then exposes its working local endpoint. Credentials, database storage, configuration, and backups remain your application responsibilities.

Which port should the tunnel target?

Use the endpoint reachable from the Localtonet client, usually the published host port for a host-based client. Do not assume the container’s internal port is the same. This guide does not establish a Chhoto URL default port.

Does restarting prove that database storage is persistent?

No. Restart preserves the container writable layer. After backing up, recreate the container with the same verified persistent mount and confirm that records survive. Separately test restoration from backup.

Do public redirects require public link creation?

No. Readers can need anonymous redirect access while link management remains authenticated. Public mode enables anonymous creation and changes the abuse model. Test these permissions independently.

Will links work when my host or tunnel is offline?

No. The application must be reachable, the selected client must be connected, and the tunnel must be running. A saved tunnel configuration does not provide an always-available copy of your short-link database.

What must a recoverable backup include?

Keep a consistent SQLite backup, the matching deployment configuration and image reference, and securely stored credentials needed to reconstruct the service. Test a restored copy in an isolated instance rather than relying on file existence alone.

Publish your verified shortener with Localtonet

Once local redirects, authentication, persistence, and recovery pass your checks, connect the HTTP endpoint to our platform. Start the tunnel, align the public site URL, and verify anonymous redirect access from an external network before sharing production links.

Get Started Free →

Corrections & updates

Substantive changes approved by the Localtonet editorial team are listed transparently below.

Replace repetitive evidence-limit disclaimers and generic advice with one verified, release-scoped deployment path. Obtain the official installation guide and Compose definition through the project resources, then explain runtime prerequisites, image selection, exact configuration and secret handling, persistent storage, listener binding, startup, status and logs, local verification, and applying the public site URL. Include concrete backup and isolated restore procedures, safe container recreation to verify persistence, upgrade and r

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support