A URL feels private because only the people you send it to should have it. In practice, a URL is one of the most-logged pieces of data on the web: it is written down, copied, and forwarded by systems that never asked your permission and that you cannot audit. Here's exactly where it leaks, and how to design links that survive it.
The leakage surface
- Browser history. Every URL the user visits is stored locally and can sync to other devices — and to the browser vendor's sync service.
- HTTP server access logs. Most servers log the full request path and query string by default. Anyone with log access has the URL.
- Reverse proxies and CDNs. Cloudflare, Fastly, your load balancer — all log the URL, each with its own retention policy.
- Log aggregation pipelines. Access logs rarely stay on one machine; they're shipped to centralized logging and monitoring services, multiplying the number of systems holding the URL.
- Referer headers. When the page links to a third-party
resource, the browser can send the source URL in the
Refererheader. Analytics scripts, embedded videos, and ad pixels all receive it. - Browser extensions. Extensions with "read browsing history" permission see every URL, including fragments.
- Bookmark sync. Bookmarked URLs sync across the user's devices and to the browser vendor's servers.
- URL preview unfurlers. Slack, Teams, iMessage, LinkedIn, and email clients fetch URLs to render previews — silently, the moment the message is sent.
- Corporate proxies and email gateways. Many organizations inspect, rewrite, and log every URL that crosses their boundary.
What HTTPS does and doesn't protect
Worth being precise, because "the connection is encrypted" gets misread as "the URL is private."
TLS encrypts the path and query string in transit: a network
observer between the browser and the server cannot read
/reset?token=.... What TLS does not hide: the destination
hostname (visible via DNS resolution and the TLS SNI field on most
connections today), and anything at the endpoints — the browser
still records the full URL in history, and the server still writes
it to its access log. Encryption in transit protects the wire, not
the two ends, and the two ends are where URLs leak.
A worked example: one URL's life
Trace a single sensitive URL — say a reset link,
https://example.com/reset?token=8f3a91... — from send to click:
- The mail provider stores the message (URL included) indefinitely, and its security scanner may fetch the URL on delivery.
- The recipient's email gateway rewrites and logs the URL, then fetches it again at click time.
- DNS resolves
example.com— the hostname (not the path) is now in resolver logs. - The web server writes the full request line to its access log. A default nginx/Apache "combined" format entry looks like:
203.0.113.7 - - [14/Jul/2026:09:12:45 +0000]
"GET /reset?token=8f3a91... HTTP/1.1" 200 5123 "-" "Mozilla/5.0 ..."
- The CDN in front of it logs the same URL with its own retention.
- The page's analytics script records the page URL — query string and all — in a third-party system, unless explicitly configured to strip it.
- The browser stores the URL in history and syncs it.
One send, seven copies, held by at least five organizations. None of this required an attacker — it's the web working as designed.
Referer header in practice
If your secret reveal page links to any external resource (a CDN
font, an analytics script, an embedded image), the browser may send
the page's URL in the Referer header to that origin.
Modern browsers default to strict-origin-when-cross-origin, which
trims cross-origin referrers to just the origin
(https://example.com/ rather than the full path). That's real
progress, but not something to rely on: same-origin requests still
carry the full URL, a page-level policy can loosen the default, and
older clients and embedded webviews vary.
Two practical mitigations:
<!-- Strip the referer for outbound requests on the page -->
<meta name="referrer" content="no-referrer" />
Referrer-Policy: no-referrer
The header version applies sitewide; the meta tag applies to the specific page. Use both for defense in depth — and better still, serve sensitive pages with no third-party resources at all, so there is nothing to send a referrer to.
Path vs query vs fragment
Where you put a token in the URL changes who sees it:
| Location | Sent to server? | In server logs? | In Referer? | In browser history? |
|---|---|---|---|---|
Path (/s/abc123) |
Yes | Yes | Policy-dependent | Yes |
Query (?token=abc123) |
Yes | Yes | Policy-dependent | Yes |
Fragment (#abc123) |
No | No | No | Yes |
The fragment is genuinely different: per RFC 3986 it's processed client-side and never included in the HTTP request, which is why end-to-end encrypted paste tools put the decryption key there — the server literally never receives it. It's still not invisible: browser history, extensions, and any JavaScript running on the page can read it. Path versus query, by contrast, is a distinction without a privacy difference — both land in the same logs.
The password-reset-token problem
Sensitive tokens in URLs are a recognized vulnerability class — CWE-598 covers exactly this: sensitive data in query strings ends up in logs, histories, and referrers regardless of transport encryption. It's why well-built password-reset flows never rely on the secrecy of the URL alone. They make the token single-use, short-lived, and bound to a subsequent action (setting a new password), so a token recovered from a log three weeks later is worthless.
That's the general lesson: you usually cannot keep a URL out of the logs, but you can make the logged URL worthless by the time anyone reads it.
Designing URLs that are safe to leak
Assume every URL you mint will eventually surface somewhere unintended, and design so that it doesn't matter:
- One-time consumption. Burn the secret after the first successful read so a leaked URL is dead by the time it appears in someone's history.
- Short expiry. Bound the leakage window. A URL that worked for five minutes is worth far less than one that worked for a week.
- Out-of-band passphrase. Require a secret that was never in the URL. Now the URL alone — the only thing the logs have — is insufficient.
- Separate fetch from reveal. Serve metadata on
GETand release the payload only on an explicitPOST, so preview bots and scanners can't consume the secret. - No third-party resources on sensitive pages, plus
Referrer-Policy: no-referrerand<meta name="robots" content="noindex">. - Redact secret routes in your own logs. In nginx, map the request URI before logging:
map $request_uri $loggable_uri {
~^/s/ "/s/[redacted]";
default $request_uri;
}
log_format redacted '$remote_addr - $remote_user [$time_local] '
'"$request_method $loggable_uri" $status';
access_log /var/log/nginx/access.log redacted;
You control only your own logs — the CDN, proxy, and browser copies remain — which is exactly why the first three items on this list carry most of the weight.
How this shapes secret links
This threat model is why LinkPilot's Secret Links work the way they
do: the URL is treated as inevitably-leaked, so the URL alone is
never enough. A GET returns only metadata; the payload requires an
explicit POST reveal, optionally gated by a passphrase that's
SHA-256 hashed client-side. Burned, expired, or revoked secrets
return HTTP 410 with the payload irreversibly overwritten, reveal
responses carry Cache-Control: no-store, and view events log a
hashed IP under a daily-rotating salt rather than the raw address.
And one candid limit, stated on the
security architecture page: this is not
end-to-end encryption — the honest guarantees are short expiry,
burn-after-read, and the out-of-band passphrase.
Try it free, no signup
Burn-After-Read Secret
Share a message that destroys itself after one view.
Limits
URL design can't fix everything. It doesn't control what the recipient does after a legitimate read, it can't purge copies that third parties already hold, and it won't save a URL that carries personal data in plain sight — if a user's email address is in the query string, no expiry makes that unlogged. The durable rule: never put data in a URL that you'd need to delete later, because deleting a URL from everywhere it has been copied is practically impossible. Design URLs that are worthless when leaked instead.