URI stands for Uniform Resource Identifier. It is a string that identifies a resource. A URL is a type of URI that also tells you how to access the resource. Every URL is a URI, but not every URI is a URL. That distinction sounds academic until you are reading API documentation, debugging HTTP requests, or trying to understand why a specification uses “URI” when you expected “URL.”
This guide explains what URIs are, how they differ from URLs and URNs, what the parts of a URI mean, and how they show up in web development and server administration.
URI vs URL vs URN#
These three terms describe overlapping concepts, and the confusion around them has existed since the specifications were written.
URI (Uniform Resource Identifier) is the umbrella term. A URI identifies a resource. That resource could be a web page, a file, a mailbox, a book, or an abstract concept. The identifier might tell you where the resource is, or it might just give the resource a unique name. Both of those are URIs.
URL (Uniform Resource Locator) is a URI that tells you how and where to access the resource. It includes a scheme (like
https://
) and a network location (like
www.example.com
). A URL is actionable. You can put it in a browser and get something back.
https://www.example.com/blog/article?id=123
This is both a URI and a URL. It identifies the resource (the article with id 123) and tells you how to access it (HTTPS protocol, on the server www.example.com, at the path /blog/article).
URN (Uniform Resource Name) is a URI that gives a resource a persistent, location-independent name. It identifies the resource but does not tell you where to find it or how to access it.
urn:isbn:978-0-13-468599-1
This is a URI and a URN. It identifies a specific book by its ISBN. But you cannot put it in a browser and get the book. You need some other system (a library catalog, Amazon, a bookstore) to locate it. The URN is just the name.
The practical takeaway
In day-to-day web development and server administration, the distinction between URI and URL rarely matters. When you see “URI” in documentation, you can mentally substitute “URL” in most cases and be correct. The RFC specifications use “URI” as the technically precise term, but virtually every URI you encounter in web work is a URL.
The main place the distinction matters is in API specifications and HTTP standards. The HTTP specification refers to the target of a request as a “request URI” (technically correct) rather than a “request URL” (common usage). If you are reading RFC 7230 and see “request-target,” it means the URI in the HTTP request line.
Anatomy of a URI#
A URI has up to five components, defined by RFC 3986:
scheme://authority/path?query#fragment
Using a concrete example:
https://www.example.com:443/blog/article?id=123&lang=en#comments
Scheme
https
The scheme identifies the protocol or the naming system. For web URLs, this is typically
http
or
https
. Other common schemes include
ftp
,
ssh
,
mailto
,
tel
, and
file
.
The scheme is followed by
://
for URLs that include an authority component. The
mailto
scheme is an exception.
mailto:user@example.com
uses a colon without the double slash because it has no authority component.
Authority
www.example.com:443
The authority identifies the server (or naming authority) responsible for the resource. For web URLs, this is the domain name and optionally a port number. The full syntax is:
[userinfo@]host[:port]
- userinfo is rarely used in modern web URLs but exists in the spec.
user:password@example.comis valid syntax, though browsers have largely deprecated showing credentials in the URL bar for security reasons. - host is the domain name or IP address.
- port is the network port. If omitted, the default port for the scheme is used (80 for HTTP, 443 for HTTPS, 21 for FTP, 22 for SSH).
In practice, you almost never include the port in a URL because HTTPS on port 443 is the default. You only specify it when using a non-standard port, like
http://localhost:3000
during development.
Path
/blog/article
The path identifies the specific resource on the server. It is hierarchical, using forward slashes to separate segments. The path can represent a file on disk (
/images/logo.png
), a route handled by an application (
/blog/article
), or an API endpoint (
/wp-json/wp/v2/posts
).
An empty path or a path of just
/
refers to the root resource, which is typically the homepage of a website or the base endpoint of an API.
Query
?id=123&lang=en
The query string contains key-value pairs that provide additional parameters to the resource. It starts with
?
and pairs are separated by
&
. The query does not identify a different resource. It modifies the request to the same resource.
/blog/article?id=123
and
/blog/article?id=456
point to the same endpoint but request different data.
Query strings are used extensively in:
- Search pages (
?q=search+term) - Pagination (
?page=2) - Filtering and sorting (
?category=hosting&sort=date) - Tracking parameters (
?utm_source=newsletter) - API requests (
?per_page=10&orderby=date)
Fragment
#comments
The fragment identifier points to a specific section within the resource. It starts with
#
. Importantly, the fragment is never sent to the server. It is handled entirely by the client (browser). When you click a link to
https://example.com/article#comments
, the browser requests
https://example.com/article
from the server and then scrolls to the element with
id="comments"
on the page.
Because fragments are client-side only, they do not appear in server access logs. If you are debugging a request and wondering why the fragment is missing from your Nginx logs, that is expected behavior.
Single-page applications (SPAs) use fragments for client-side routing:
https://app.example.com/#/dashboard
,
https://app.example.com/#/settings
. The server always serves the same page, and the JavaScript application reads the fragment to determine which view to render. This is the “hash routing” pattern.
Absolute vs relative URIs#
Absolute URI
An absolute URI includes the scheme and authority, providing everything needed to locate the resource:
https://www.example.com/blog/article
You can give this to anyone and they can access the resource regardless of context.
Relative URI
A relative URI omits the scheme and authority and is resolved relative to a base URI (usually the current page):
/blog/article
../images/logo.png
article?id=123
Relative URIs are common in HTML. When you write
<a href="/about">About</a>
in your HTML, the browser resolves it against the current page’s URL. If you are on
https://example.com/blog/
, the link resolves to
https://example.com/about
.
Protocol-relative URIs omit only the scheme:
//cdn.example.com/style.css
The browser uses the same protocol as the current page (HTTPS if the page is HTTPS, HTTP if HTTP). This pattern was common when sites served both HTTP and HTTPS, but now that HTTPS is standard, it is better to use explicit
https://
everywhere. Protocol-relative URIs are still valid but considered a legacy pattern.
Why this matters for WordPress
WordPress stores full absolute URLs in the database for post content, images, and links. When you migrate a WordPress site to a new domain, every URL in the database needs to be updated. This is why WordPress migrations require a search-and-replace operation on the database, and why simply copying files is not enough. Relative URLs would avoid this problem, but WordPress made the design decision to use absolute URLs, and that decision is deeply embedded in the codebase.
URIs in HTTP requests#
When your browser sends an HTTP request, the request line contains the URI:
GET /blog/article?id=123 HTTP/1.1
Host: www.example.com
The request URI here is
/blog/article?id=123
, which is the path and query without the scheme or authority. The authority is in the
Host
header instead. This is how HTTP/1.1 works: the URI in the request line is relative, and the
Host
header provides the domain.
In HTTP/2 and HTTP/3, the components are split across pseudo-headers (
:method
,
:path
,
:authority
,
:scheme
), but the concept is the same.
When you see a 405 Method Not Allowed error, the server is saying it understood the URI but does not support the HTTP method you used on it. When you see a 404 Not Found, the server could not find a resource matching the URI path.
URIs in REST APIs#
REST APIs use URIs as resource identifiers. The WordPress REST API is a practical example:
GET /wp-json/wp/v2/posts # List all posts
GET /wp-json/wp/v2/posts/123 # Get post with ID 123
POST /wp-json/wp/v2/posts # Create a new post
PUT /wp-json/wp/v2/posts/123 # Update post 123
DELETE /wp-json/wp/v2/posts/123 # Delete post 123
The URI
/wp-json/wp/v2/posts/123
identifies a specific resource (post 123). The HTTP method (GET, POST, PUT, DELETE) determines what operation is performed on that resource. The same URI with different methods produces different results.
This is the core principle of REST: URIs identify resources, HTTP methods define actions. A well-designed REST API has predictable URI patterns:
/resource # Collection
/resource/{id} # Specific item
/resource/{id}/related # Related sub-resource
Query parameters filter or modify the collection:
/wp-json/wp/v2/posts?per_page=5&orderby=date&order=desc
URI encoding#
URIs can only contain a specific set of characters. Letters, digits, hyphens, periods, underscores, and tildes are safe. Everything else must be percent-encoded: the character’s byte value expressed as
%XX
in hexadecimal.
Common encodings:
| Character | Encoded | Where you see it |
|---|---|---|
| Space |
%20
or
+
| Search queries, file names |
/
|
%2F
| When a literal slash is needed in a path segment |
?
|
%3F
| When a literal question mark is needed outside the query |
&
|
%26
| When a literal ampersand is needed in a query value |
#
|
%23
| When a literal hash is needed (not a fragment) |
@
|
%40
| Email addresses in URIs |
If you see
%20
in a URL, it is a space.
https://example.com/my%20file.pdf
points to a file named “my file.pdf”. Browsers display the decoded version in the address bar but send the encoded version in the HTTP request.
When building URLs programmatically, use your language’s built-in encoding functions rather than manually replacing characters. In PHP:
urlencode()
for query values,
rawurlencode()
for path segments. In JavaScript:
encodeURIComponent()
for individual values,
encodeURI()
for complete URIs.
Common URI mistakes#
Confusing path and query in Nginx. Nginx’s
$uri
variable contains the path without the query string.
$request_uri
contains both. If you use
$uri
in a redirect and the original request had query parameters, they are lost. Use
$request_uri
when you need to preserve the full URI:
# Preserves query string
return 301 https://example.com$request_uri;
# Loses query string
return 301 https://example.com$uri;
Forgetting to encode special characters. A URL containing unencoded spaces, brackets, or Unicode characters may work in some browsers but fail in API calls, curl, and automated tools. Always encode.
Using fragments for server-side routing. Fragments are never sent to the server. If your application depends on the server seeing
#section
in the URL, it will not work. Use query parameters or path segments instead.
Quick reference#
| Term | Definition | Example |
|---|---|---|
| URI | Identifies a resource (umbrella term) |
https://example.com/page
|
| URL | URI that locates a resource (includes scheme + authority) |
https://example.com/page
|
| URN | URI that names a resource (location-independent) |
urn:isbn:978-0-13-468599-1
|
| Scheme | Protocol or naming system |
https
,
ftp
,
mailto
|
| Authority | Server identifier (host + optional port) |
www.example.com:443
|
| Path | Resource location on the server |
/blog/article
|
| Query | Key-value parameters |
?id=123&lang=en
|
| Fragment | Client-side section identifier (not sent to server) |
#comments
|