Localhost is the name your computer uses for itself. Type it into a browser and the request never reaches a network: it goes out of the browser, turns around inside the machine, and arrives at whatever is listening on that port a few microseconds later.

That is the whole definition, and it is genuinely simple. What is not simple is that the browser treats this address differently from every other address on purpose, in several ways at once. So a page served from localhost is not the same page served from a domain, and the gaps between them are where a particular kind of bug lives: the one that is invisible until the moment you deploy.

localhost, 127.0.0.1, and the third one nobody mentions

These get used interchangeably and are not the same thing.

127.0.0.1
An address. The IPv4 loopback, hard-wired to mean this machine. No lookup involved
::1
The same idea in IPv6, and a different address. A server bound only to IPv4 is not listening here
localhost
A name that resolves to one of the above. Usually both, and the order is the machine's choice
0.0.0.0
Not an address you visit. It means "listen on every interface", which is what makes a server reachable from your phone on the same wifi

That third row is the source of a specific, maddening afternoon. localhost is a hostname, and the machine resolves it. On a system that prefers IPv6, localhost becomes ::1, and a server bound only to 127.0.0.1 is not listening on ::1. The result is a connection refused on localhost and a perfectly working page on 127.0.0.1, which reads like the machine contradicting itself. It is not. They are two addresses and your server is on one of them.

Why the browser bends its own rules here

This is the part that matters for bugs, and most explanations of localhost leave it out entirely.

Browsers require a secure context for a long list of capabilities: service workers, the clipboard API, geolocation, the camera and microphone, notifications, and more. Secure context normally means HTTPS. But localhost is treated as potentially trustworthy and gets the exemption, because the traffic never leaves the machine and there is nothing in between to intercept it.

The reasoning is sound and the consequence is a trap. Everything in that list works on http://localhost and stops working the moment the same code is served from http://your-staging-box over plain HTTP. Nothing in your development run told you the feature depended on a secure context, because the rule was waived for you locally and enforced everywhere else.

The other differences, which all point the same way

Localhost is not a small version of production. It is a different environment that happens to run the same code, and almost every difference flatters you.

There is no network. No latency, no packet loss, no flaky wifi. Every race condition that depends on one request arriving after another resolves the fast way locally and the other way for somebody on a train. Loading spinners nobody ever sees are usually this.

There is no CDN, no proxy, no load balancer. The compression, caching, header rewriting and request buffering that sit in front of production are all absent. A response that works locally can be transformed by something in the middle before a real user sees it.

The filesystem is probably case-insensitive. On macOS and Windows, Logo.svg and logo.svg are the same file. On the Linux box you deploy to, they are not, and the import that worked on every developer’s machine 404s in production.

Cookies behave differently. localhost is special-cased by browsers for secure cookies, and it has no registrable domain, so SameSite and cross-subdomain behaviour that you never exercise locally is exercised immediately in production.

You are one user. No concurrency, no connection pool under pressure, no cache that another request already warmed.

Each of those makes the local run easier than the real one. That is the pattern worth noticing: localhost does not fail differently, it fails less, which is exactly what makes “it works on localhost” a weak signal rather than a reassuring one.

What the phrase actually means

When somebody says a thing works locally and not in production, they have not made a claim about the code. They have made a claim about two environments, and the useful question is which difference is responsible.

That is a short list, and it is the same list every time: the secure context exemption, the absence of a network, the absence of the middle boxes, filename case, cookie scope, and load. A bug that appears on deploy and not locally is nearly always one of those six, and knowing them turns “works on my machine” from an accusation into a checklist.

It is also why a staging environment exists, and why it too is never quite a copy.

Finding out which one it was

The difficulty with an environment bug is that you cannot see it from the environment you are in. The person hitting it is on the other side, with a page that does not work and no way to tell you why, and your machine keeps insisting everything is fine.

Session Replay

Free Chrome extension. One click on the page that is misbehaving captures the screenshot, the console and the network log, and hands you a link to paste into the ticket.

Get the extension

The console is where a secure context failure announces itself, and the network log is where a proxy’s rewriting, a 404 on a file that exists locally, or a cookie that was never sent all show up. Captured from the machine where it actually broke, those two answer the question your own machine cannot.

In one paragraph

Localhost is your computer’s name for itself, resolving to 127.0.0.1 or ::1, and a server bound to one of those is not listening on the other, which is the whole explanation for localhost refusing a connection that 127.0.0.1 accepts. More importantly, browsers treat it as a secure context even over plain HTTP, so service workers, the clipboard, geolocation and the camera all work locally and can stop working the moment the same code is served over HTTP from anywhere else. Add the absent network, the absent proxies and CDN, a case-insensitive filesystem, different cookie scoping and a load of exactly one, and localhost is not a small production: it is an environment that fails less, which is why “it works on localhost” narrows nothing down on its own.