# URI vs URL (and URN)

> A URL is a URI that says where a resource lives; a URI only identifies it. See the parts of a URL, what the server never receives and parser pitfalls.

Source: https://howhttpworks.com/glossary/uri-vs-url
Last reviewed: 2026-10-04

> **TL;DR:** URI is the umbrella term for any identifier. A URL is a URI that also locates the resource. In practice, web specs and developers say "URL" for everything you type into an address bar.

A URI (Uniform Resource Identifier) is a string that identifies a resource according to RFC 3986. A URL (Uniform Resource Locator) is a URI that gives the means to reach it, such as a scheme plus network location. A URN names a resource without locating it. All URLs are URIs; the reverse is not true.

## Examples

| String | URI? | URL? |
| --- | --- | --- |
| `https://example.com/docs?id=7#intro` | Yes | Yes |
| `mailto:dev@example.com` | Yes | Yes (names a scheme and an address) |
| `urn:isbn:0451450523` | Yes | No (a name, not a location) |
| `/docs?id=7` | Yes (relative reference) | Not by itself |

## Parts, and what the server sees

```text
https://user@example.com:8443/docs/page?id=7&sort=asc#intro
\___/   \__/ \_________/ \__/ \________/ \__________/ \___/
scheme userinfo host    port  path       query         fragment
```

A browser sends this on the wire:

```http
GET /docs/page?id=7&sort=asc HTTP/1.1
Host: example.com:8443
```

The scheme becomes the connection type, the host and port become `Host` and the TCP target, and the path and query form the request target. The `#intro` fragment is never sent.

## Non-obvious facts

- **The request target is not the URL.** In HTTP/1.1 the request line holds only the path and query (origin-form); the host travels in `Host`. A proxy request uses the full URL (absolute-form).
- **Userinfo in `http(s)` URLs is deprecated.** RFC 9110 tells senders not to generate `user:pass@` in http URIs, and browsers strip or warn on it.
- **Parsers disagree.** RFC 3986 and the WHATWG URL Standard parse some malformed inputs differently (backslashes, extra slashes, odd hosts), which has caused real SSRF and allowlist bypasses when one component validates and another fetches. Parse once, with one library, and reuse the result.
- **Percent-encoding rules are context-specific.** A `+` means space only in `application/x-www-form-urlencoded` query data, not in paths.
- **A URI identifies; HTTP dereferences.** `http://example.com/` can be fetched; a namespace URI like `http://www.w3.org/1999/xhtml` is just an identifier.

## Go deeper

- [Host header](https://howhttpworks.com/headers/host)
- [Location header](https://howhttpworks.com/headers/location)
- [Referer header](https://howhttpworks.com/headers/referer)
- [How HTTP works](https://howhttpworks.com/guides/how-http-works)
