# Free static site hosting with no signup

Put a folder or a single HTML file on a public HTTPS URL with one command and no account: `curl -fsSL https://chorus.host/publish.py | python3 - ./site`. The site is live at `https://<name>.chorus.host` for 24 hours; open the claim link and sign in with an emailed code to keep it.

## The one command

Run it from the folder that holds your site (or point it at a single file):

```bash
curl -fsSL https://chorus.host/publish.py | python3 - ./site
```

It prints JSON like this (claim token shortened here):

```json
{
  "url": "https://navy-twig-20.chorus.host",
  "slug": "navy-twig-20",
  "anonymous": true,
  "expiresAt": "2026-10-02T05:30:23Z",
  "files": 2,
  "claimUrl": "https://chorus.host/claim/navy-twig-20#ctk_REDACTED",
  "claimToken": "ctk_REDACTED"
}
```

Live at the `url` for 24 hours. To keep it, open the `claimUrl`, enter your email and the 6-digit code we send you.

Measured on 1 October 2026 (one run, two files, from a Mac in the San Francisco Bay Area): 3.0 seconds from pressing Enter to a published site, including downloading the script, and 3.4 seconds until the link answered `200`.

Only have curl? One HTML file is one request:

```bash
curl -sS https://chorus.host/v1/publish -F file=@index.html
```

Or drop a file, a folder or a .zip here:

<!-- html:dropzone -->

## Pick a way to publish

| You have | Use |
|---|---|
| A folder or a file, and Python 3 | `curl -fsSL https://chorus.host/publish.py \| python3 - ./site` |
| One file and curl, or a zipped folder | `curl -sS https://chorus.host/v1/publish -F file=@index.html` (or `-F archive=@site.zip`) |
| A script or CI job publishing big folders | [The HTTP API in three calls](#the-http-api-in-three-calls) |
| An agent with a shell (Claude Code, Codex, Cursor) | `npx skills add chorus-host/skill -g`, or the one command above |
| An agent with no shell (claude.ai, other MCP clients) | The MCP server `https://chorus.host/mcp`, tool `publish_site` ([setup](/mcp)) |
| A build folder you deploy again and again | `beacon deploy ./dist` ([the CLI](#the-beacon-cli)) |
| No terminal at all | The drop zone above, or [chorus.host/html-to-url](/html-to-url) |

## Host a static site without an account in one command

`publish.py` is a standard-library Python script; nothing to install. What it does:

- Publishes a folder, or a single file. A lone `.html` file becomes `index.html`, so the link opens it.
- Takes an optional name: `python3 - ./site my-site` publishes to `https://my-site.chorus.host`.
- Remembers what it published in `.beacon/publish.json`, so running it again from the same folder updates the same site and keeps the URL.
- Never uploads `.git` and other version-control folders, `node_modules`, virtualenvs, anything your `.gitignore` or `.beaconignore` matches, or files that usually hold secrets: `.env` and `.env.*`, `.npmrc`, `.pypirc`, `.netrc`, SSH keys, `*.pem`, `*.key`, `*.p12`, `*.pfx`. Each skipped path is listed.
- Prints one JSON object: `url`, `slug`, `expiresAt`, `claimUrl`, `claimToken`, plus `fileUrl` when you published a single file.
- Reads `CHORUS_CLIENT=<agent>/<version>` if you want to tell us which agent sent the publish.

If you have an API key in `CHORUS_API_KEY`, `BEACON_API_KEY` or `~/.config/beacon/config.json`, the site goes into your account and doesn't expire. Pass `--anonymous` to ignore the key.

## Publish from an AI agent and let a human claim it later

Agents that can run commands only need the one command above, or the skill: `npx skills add chorus-host/skill -g` teaches Claude Code, Codex, Cursor and Gemini CLI how to publish and what to tell you afterwards.

Agents without a shell use the MCP server. In Claude Code:

```bash
claude mcp add --transport http chorus https://chorus.host/mcp
```

In any client that takes a JSON config (`mcp.json`):

```json
{ "mcpServers": { "chorus": { "type": "http", "url": "https://chorus.host/mcp" } } }
```

On claude.ai, add `https://chorus.host/mcp` as a custom connector. The `publish_site` tool takes up to 4 MB of files per call without an API key (10 MB with one) and up to 1,000 files. [All clients, including ChatGPT's limits](/mcp).

Paste this to your agent:

```text
Publish this folder to chorus.host and give me the link and the claim link, exactly as returned. Instructions: https://chorus.host/skill.md
```

The hand-off rule the skill gives agents: show the live URL on its own line, and the claim link character for character. The code after `#` in the claim link is 47 characters; if an agent shortens or rewraps it, the link breaks.

## The HTTP API in three calls

One request (`/v1/publish`) is the simple path for small sites: up to 4 MB of files without an account. The three-call API handles big sites: register the files with their sizes and SHA-256 hashes, upload each one to the URL you get back, then make the version live. The hash is what lets chorus.host skip files it already has, so re-publishing a big folder only uploads what changed. One file:

```bash
FILE=index.html
HASH="sha256:$( (shasum -a 256 "$FILE" 2>/dev/null || sha256sum "$FILE") | cut -d' ' -f1)"
SIZE=$(wc -c < "$FILE" | tr -d ' ')
curl -sS https://chorus.host/v1/sites -H "Content-Type: application/json" \
  -d '{"files":[{"path":"index.html","size":'"$SIZE"',"contentType":"text/html; charset=utf-8","hash":"'"$HASH"'"}]}' \
  > site.json
UPLOAD_URL=$(jq -r '.uploads.pending[0].uploadUrl // empty' site.json)
[ -z "$UPLOAD_URL" ] || curl -sS -X PUT "$UPLOAD_URL" \
  -H "Content-Type: text/html; charset=utf-8" --data-binary @"$FILE"
curl -sS -X POST "https://chorus.host$(jq -r .version.finalizeUrl site.json)" \
  -H "X-Claim-Token: $(jq -r .claimToken site.json)"
jq -r '"\nLive: \(.site.url)\nClaim it to keep it: \(.claimUrl)"' site.json
```

To choose the name, add `"slug":"my-site"` next to `"files"`. Names are 3 to 63 lowercase letters, digits and hyphens.

### A whole folder with curl

<details>
<summary>publish-dir.sh: the same three calls for every file in a folder (bash, curl, jq)</summary>

Save this as `publish-dir.sh` and run `bash publish-dir.sh ./site`. It sends every file in the folder (hidden files are skipped) and prints the URL. If `BEACON_API_KEY` is set, the site goes into your account instead of expiring.

```bash
#!/usr/bin/env bash
# publish-dir.sh DIR: publish a folder to chorus.host and print its URL.
# No account: the site lasts 24 hours. With BEACON_API_KEY set: it's yours.
# Needs curl, jq, and shasum or sha256sum. Files up to 250 MB each.
set -eo pipefail
cd "${1:?usage: publish-dir.sh DIR}"

mime() {
  case "$(printf '%s' "${1##*.}" | tr '[:upper:]' '[:lower:]')" in
    html|htm) echo "text/html; charset=utf-8" ;;
    css) echo "text/css; charset=utf-8" ;;
    js|mjs) echo "text/javascript; charset=utf-8" ;;
    json|map) echo "application/json" ;;
    txt) echo "text/plain; charset=utf-8" ;;
    csv) echo "text/csv; charset=utf-8" ;;
    xml) echo "application/xml" ;;
    svg) echo "image/svg+xml" ;;
    png) echo "image/png" ;;
    jpg|jpeg) echo "image/jpeg" ;;
    gif) echo "image/gif" ;;
    webp) echo "image/webp" ;;
    ico) echo "image/x-icon" ;;
    pdf) echo "application/pdf" ;;
    woff2) echo "font/woff2" ;;
    wasm) echo "application/wasm" ;;
    mp4) echo "video/mp4" ;;
    *) echo "application/octet-stream" ;;
  esac
}
sha256() { { shasum -a 256 "$1" 2>/dev/null || sha256sum "$1"; } | cut -d' ' -f1; }

manifest=$(find . -type f ! -path '*/.*' | sed 's|^\./||' | while IFS= read -r file; do
  jq -n --arg path "$file" --argjson size "$(wc -c < "$file" | tr -d ' ')" \
    --arg type "$(mime "$file")" --arg hash "sha256:$(sha256 "$file")" \
    '{path: $path, size: $size, contentType: $type, hash: $hash}'
done | jq -s '{files: .}')

auth=()
if [ -n "$BEACON_API_KEY" ]; then
  auth=(-H "Authorization: Bearer $BEACON_API_KEY")
fi

res=$(curl -sS --fail-with-body https://chorus.host/v1/sites "${auth[@]}" \
  -H "Content-Type: application/json" -d "$manifest")

jq -r '(.uploads.pending // [])[] | select(.uploadUrl) | [.path, .uploadUrl] | @tsv' <<<"$res" |
while IFS=$'\t' read -r file url; do
  curl -sS --fail-with-body -X PUT "$url" -H "Content-Type: $(mime "$file")" \
    --data-binary @"$file" >/dev/null
done

token=$(jq -r '.claimToken // empty' <<<"$res")
if [ -n "$token" ]; then
  auth=(-H "X-Claim-Token: $token")
fi
curl -sS --fail-with-body -X POST "https://chorus.host$(jq -r .version.finalizeUrl <<<"$res")" \
  "${auth[@]}" >/dev/null

jq -r '"Live: \(.site.url)", (.claimUrl // empty | "Claim it to keep it: \(.)")' <<<"$res"
```

</details>

## The beacon CLI

```bash
curl -fsSL https://chorus.host/install.sh -o install-beacon.sh && sh install-beacon.sh
beacon deploy ./site --pretty
```

On Windows PowerShell, install with `iwr https://chorus.host/install.ps1 -useb | iex` and run the same `beacon deploy`. If the folder has a `package.json` with a build script, the CLI runs `npm run build` first and uploads `dist/`, `build/` or `out/`. It saves the claim token in `.beacon/state.json`, so after `beacon login`, running `beacon deploy` again from the same place claims the site.

## Claim the site later {#claim-the-site-later}

Every site published without an account comes with a `claimUrl` like `https://chorus.host/claim/navy-twig-20#ctk_...`.

- **Treat it like a password.** Whoever opens it can take the site. Don't post it publicly.
- **How a person claims:** open the link, enter an email address, enter the 6-digit code we send. There's no password to set.
- **Any device works.** The claim code is in the part of the link after `#`, which browsers never send to a server, so the link can be opened on a phone or forwarded to a colleague.
- **With an API key:**

```bash
curl -sS -X POST "https://chorus.host/v1/sites/$SLUG/claim" \
  -H "Authorization: Bearer $CHORUS_API_KEY" -H "Content-Type: application/json" \
  -d '{"claimToken":"'"$TOKEN"'"}'
```

- **With the tools:** run `beacon login`, then `beacon deploy` from the same folder; or set `CHORUS_API_KEY` and run `publish.py` again. Both claim the saved site automatically.
- **If nobody claims it:** after 24 hours the site is deleted, its URL shows a "publish yours" page, and the claim link stops working. Deleted sites can't be restored.

Claiming removes the expiry. After that, manage the site with `Authorization: Bearer` instead of `X-Claim-Token`.

## No account vs free account

| | No account | Free account |
|---|---|---|
| How long the site lasts | 24 hours, then it is deleted ([how expiry works](/guides/temporary-website-hosting)) | Until you delete it, or an expiry you set |
| Publishes per hour | 5 per IP address | 60 per account |
| Largest file | 250 MB | 5 GB |
| Files per site | 10,000 | 10,000 |
| Total size per site | 10 GB | 10 GB |
| Password protection | Yes | Yes |
| Search engines | Sent `X-Robots-Tag: noindex` until claimed | Can be indexed |
| Custom domain | No | No |
| Workers (APIs, webhooks) | No | [Yes](/workers) |

Custom domains aren't available on any plan yet. Every site lives at `<name>.chorus.host`.

## Other ways to publish without an account

Checked 1 October 2026:

- **Netlify:** Netlify Drop and `netlify deploy --allow-anonymous` work with no account, but the site is password-protected until claimed and removed after 1 hour if nobody claims it.
- **Cloudflare:** `npx wrangler deploy --temporary` (and Cloudflare Drop in the browser) gives a public `workers.dev` link with 60 minutes to claim it.
- **surge.sh and tiiny.host:** both need an account; surge creates one in the terminal on first run.

[Netlify Drop, tiiny.host and surge.sh compared in full, with measured times](/vs/netlify-drop-tiiny-host-surge), and [how chorus.host compares with here.now](/vs/here-now).

When another host fits better:

- **A custom domain:** claim on Netlify or Cloudflare, or use here.now's free plan, which includes one custom domain.
- **Framework builds on every push:** Vercel or Netlify connected to your git repo.
- **A link that should outlive a day with no account:** ship.page keeps anonymous drops for 30 days.
- **A Worker with KV or D1:** `wrangler deploy --temporary` includes them; chorus.host Workers have no built-in storage.

Need an API next to the site? [Deploy it as a chorus.host Worker](/workers#static-site-api). Sharing one HTML report? [Share an HTML file as a link](/guides/share-html-file-as-link) has the password, update and expiry steps.

## Good to know

- A path that doesn't match a file returns 404. There is no fallback to `index.html`, so single-page apps need hash routing or one HTML file per route.
- A folder URL serves that folder's `index.html`. A folder without one shows a file list.
- Files are served with the content type you declare and `X-Content-Type-Options: nosniff`, so a `.css` file declared as `text/plain` won't load as a stylesheet.
- Executables and installers (`.exe`, `.msi`, `.dmg`, `.jar` and similar) and shell-script content types are rejected.

## Questions

### Can I host a static site without an account?

Yes. `curl -fsSL https://chorus.host/publish.py | python3 - ./site` publishes the folder to `https://<name>.chorus.host` with no account. It stays up for 24 hours; open the claim link and sign in to keep it.

### Is there free static hosting with no signup?

chorus.host is free with or without an account. Without one, sites last 24 hours and you can publish 5 an hour from one IP address. A free account, made with just an emailed code, keeps sites until you delete them.

### What's the fastest way to put a single index.html on a public URL?

`curl -sS https://chorus.host/v1/publish -F file=@index.html`. One request, no account, and the response has the link.

### How do I deploy a static site from the command line without signing up?

Run `curl -fsSL https://chorus.host/publish.py | python3 - ./dist` from your project, or install the beacon CLI and run `beacon deploy ./dist`. Both print the URL. Running either again updates the same site.

### How long does a site published without an account stay up?

24 hours from the first publish. Updating it doesn't reset the clock. Claim it to keep it, or it is deleted and the URL stops working.

### How do I claim a site later, and can my AI agent hand it to me?

Open the claim link the publish returned and sign in with your email and a 6-digit code. Agents using the chorus.host skill or MCP server are told to give you that link exactly as returned, so you can claim the site from any device.

### Can I use a custom domain?

No. Every site lives at `<name>.chorus.host`, with or without an account. If you need your own domain, Netlify, Cloudflare Pages or here.now's free plan support one.

### Is there an instant static hosting API with no signup?

Yes. `POST https://chorus.host/v1/publish` takes a file, a multipart form or a zip and returns the live URL in one request. For big sites, the three-call API (`POST /v1/sites`, upload, finalize) handles files up to 250 MB each.

### Will search engines index my site?

Not while it's unclaimed: those sites are sent with `X-Robots-Tag: noindex`. Once claimed into an account, they can be indexed like any other site.
