Fix Astro “Blocked request. This host is not allowed”
Content updated October 25, 2026.
Why does astro dev say “Blocked request. This host is not allowed” when I open my site through a tunnel?
Astro’s Vite-based dev server blocks unknown hosts. In Astro 5.4 and later, add the tunnel domain, such as .qurl.site, to server.allowedHosts in astro.config.mjs, even when the error names vite.config.js. Config edits apply automatically. With qURL™, the site stays on your machine with no public URL, and each link opens once. If you later need one permanent public hostname, ngrok’s dev domain fits.
What you need
- An Astro site running with astro dev, opened through a tunnel or qURL link
- Your astro.config.mjs
Steps
Read the host from the error
The page answers 403 with Vite’s message: “Blocked request. This host ("c-k3j2ab.qurl.site") is not allowed. To allow this host, add "c-k3j2ab.qurl.site" to server.allowedHosts in vite.config.js.” In an Astro project the setting goes in astro.config.mjs instead.
Add the tool’s domain to server.allowedHosts
A leading dot allows the domain and every subdomain: .qurl.site for qURL, your exact dev domain for ngrok, .trycloudflare.com for a Cloudflare Quick Tunnel. Without the dot, qurl.site does not cover the c-….qurl.site host a qURL link uses. vite.server.allowedHosts works too, and astro dev --allowed-hosts .qurl.site sets it for one run.
// astro.config.mjs (merge into your existing config) export default { server: { allowedHosts: ['.qurl.site'] }, }Share it through qURL
Restart astro dev with --host 127.0.0.1, then publish that loopback address from a second terminal as shown. Your app stays on this computer; nothing listens on your network. Each qurl share link opens once and expires within 24 hours.
qurl publish http://127.0.0.1:4321 qurl share <CRID>
What the recipient sees
With the domain allowed, the site loads through the link and hot reload connects.
Which tool should carry your astro dev server?
| Option | Choose it when | Trade-off |
|---|---|---|
| qURL | qURL fits when particular people should see the site in progress: one .qurl.site entry covers every qURL link, with no account. | Publish the address your dev server listens on, and create a separate qURL link for each person with qurl share. |
| ngrok | ngrok fits when you want one stable URL: your ngrok dev domain stays the same, so one exact allowedHosts entry lasts. | ngrok needs an account and authtoken, the URL is public, and free-plan browser visitors see a warning page first. |
| Cloudflare Quick Tunnels | A Cloudflare Quick Tunnel fits a no-account look: .trycloudflare.com covers the hostname that changes every run. | A Cloudflare Quick Tunnel URL is public unless you set --allowed-mail, and Quick Tunnels do not support Server-Sent Events. |
Troubleshooting
| If you see | Do this |
|---|---|
| Form posts to an on-demand page fail with “Cross-site POST form submissions are forbidden” | Astro’s security.checkOrigin rejects cross-site form posts. A browser posting a form from the same page sends Sec-Fetch-Site: same-origin, which Astro accepts, so forms on the page work through qURL. A script that sends only an Origin header is rejected, because a qURL link’s origin (r_….qurl.site) differs from the host Astro receives (c-….qurl.site). |
| Your coding agent started astro dev and it went to the background | Astro’s docs say astro dev runs in the background when it detects an AI agent on macOS or Linux. Use astro dev status, astro dev logs and astro dev stop to manage it. |
| qurl publish says the local app is not accepting TCP connections | Start the app first and check that curl works on the same address. qurl publish connects to localhost as 127.0.0.1, so if your app listens only on IPv6 (on macOS or Linux, lsof -iTCP -sTCP:LISTEN -nP shows [::1]), publish 'http://[::1]:PORT' instead, or start the dev server with --host 127.0.0.1 and publish that address. Neither requires listening on your LAN. Keep the quotes: zsh treats the brackets as a pattern. |
| The recipient sees “Access link invalid” | The link was already used or has expired. Run qurl share again and send the new link. |
| The recipient sees “Resource not found” | The resource was deleted after the link was made. Publish the app again, then run qurl share and send the new link. |
| publish rejects http://127.0.0.1 with “only HTTPS URLs are allowed”, or asks you to log in | You have a CLI older than 3.0. On macOS run brew update, then brew upgrade qurl; elsewhere install the latest release again. After updating, qurl --version should print 3.4.1 or later. |
| publish prints no Access: line, or the Access: line says public and you did not ask for public | With no Access: line, the CLI is older than 3.4.1: on macOS run brew update, then brew upgrade qurl; elsewhere install the latest release again. A resource that an older CLI published is public unless you published it with --private, and anyone who has the CRID of a public resource can open it. To make a public resource private, run qurl delete <CRID> and publish again with qurl 3.4.1 or later. The new resource is private and has a new CRID. |
| publish says this Linux session “can’t reach a systemd user manager” | You are in a container or sandbox without a user service manager. Run qurl publish with --foreground; sharing lasts as long as that process runs. |
| publish says “Quota Exceeded” with “protected resource limit reached”, or “Your qURL account has reached a plan limit” | You can see this without an account: a device with no account can keep up to 3 active resources, with links that last up to 24 hours. Run qurl list --status active, then qurl delete <CRID> for any you no longer need (it asks you to confirm; --yes skips that). qurl stop keeps the resource, so it does not free a slot. |
Frequently Asked Questions
Which Astro versions have server.allowedHosts?
Astro added server.allowedHosts in 5.4.0. On earlier versions, set vite.server.allowedHosts in astro.config.mjs, which passes the list to Vite.
Does security.allowedDomains fix form posts through qURL?
No. With allowedDomains set to **.qurl.site, Astro still compares the origin’s hostname with the host it receives, and through qURL those differ. Browser form posts pass anyway, on Sec-Fetch-Site.
Who else can reach my app while the link is open?
Opening the link admits the network address it was opened from for the session, which lasts 1 hour by default. Other devices that share that public IP address, such as people on the same office or café network or VPN, can reach the app until the session ends. By default a link admits whoever opens it; access rules set through the API, such as IP allow and deny lists, can restrict that. Keep your app’s own sign-in for anything sensitive.