
Localhost Not Working on Mac: 7 Fixes
Localhost refuses to load on your Mac? Here are 7 tested solutions: server check, hosts file, firewall, blocked ports. Step-by-step troubleshooting guide.
Manage hosts files without the terminal
ToggleHosts helps you manage environments visually on Windows, macOS, and Linux, with automatic DNS flush and backups.
One-time payment
You run npm run dev or docker compose up. Your terminal cheerfully reports:
ready - started server on 0.0.0.0:3000
URL: http://localhost:3000You switch to your browser, hit Enter, and stare at a dead end: "This site can’t be reached" or "ERR_CONNECTION_REFUSED".
You probably tried running the command again with sudo. You opened an Incognito window. You restarted your Terminal app. Maybe you even rebooted your Mac. And yet, the exact same connection refusal greets you.
When your development loop breaks on something as fundamental as localhost, it brings your work to an immediate halt. Let us diagnose and fix it systematically.
Debunking the "False Culprits"
Before running random commands, eliminate what is not causing your issue:
- Do not assume your code has a runtime crash: An unhandled exception or syntax error returns an HTTP
500 Internal Server Erroror a framework error overlay, notERR_CONNECTION_REFUSED. - Do not waste time flushing external DNS: Modern macOS resolves the literal hostname
localhostinternally at the OS loopback resolver level before querying external nameservers. - Do not reinstall Node, Docker, or Homebrew: The problem is almost always a socket binding mismatch, a ghost background process, or an altered
/etc/hostsfile.
The "Ah-Ha!" Technical Explanation
Why does localhost:3000 fail when your terminal output insists the server is active?
Understanding three low-level network mechanisms on macOS explains 90% of failures:
- IPv6 vs IPv4 Binding Scope (
::1vs127.0.0.1): Node 18+, modern Chromium, and macOS resolvelocalhostto IPv6 (::1) by default. If your development server binds strictly to IPv4 (127.0.0.1), your browser attempts to connect to::1:3000, finds no listener, and immediately throwsERR_CONNECTION_REFUSED. - Zombie Port Locks (Ghost PIDs): If you stop a server using
Ctrl+Z(which suspends the process) instead ofCtrl+C(SIGINT), the socket stays locked inLISTENstate in the background. When you start the server again, it silently shifts to port 3001, leaving you refreshing a dead port 3000. - Docker Container Namespace Isolation: Inside a Docker container,
localhostpoints to the container's internal network namespace, not your Mac. If your app inside the container listens on127.0.0.1, traffic forwarded from macOS never reaches it.
Quick Decision Tree: Find Your Exact Fix
Select your stack to jump straight to the solution:
- Node.js, Next.js, or Vite: Jump to Solution #1: Fix Node & Modern Bundler IP Bindings
- Docker or Docker Compose: Jump to Solution #2: Fix Docker Port Mapping & Container Bindings
- Port Conflict / "Port already in use": Jump to Solution #3: Identify & Kill Ghost PIDs
- Hosts File & Loopback Resolution: Jump to Solution #4: Restore macOS Loopback in /etc/hosts
- macOS Firewall & Security Apps: Jump to Solution #5: Check macOS Application Firewall
- Python (Flask, Django, FastAPI): Jump to Solution #6: Python Host Binding Configuration
- MAMP / XAMPP / Apache: Jump to Solution #7: MAMP and Web Server Port Mismatch
Solution #1: Fix Node & Modern Bundler IP Bindings
If http://127.0.0.1:3000 works in your browser but http://localhost:3000 refuses connection, your dev server is listening only on IPv4.
Test Direct IPv4 vs IPv6
Run these two commands in Terminal:
# Test direct IPv4 loopback
curl -I http://127.0.0.1:3000
# Test IPv6 loopback
curl -I http://[::1]:3000If IPv4 returns HTTP/1.1 200 OK while IPv6 fails, force your dev server to bind to all interfaces (0.0.0.0 / dual-stack).
The Fix
In Vite (`vite.config.ts`):
import { defineConfig } from 'vite';
export default defineConfig({
server: {
host: '0.0.0.0',
port: 5173,
},
});In Next.js (`package.json`):
{
"scripts": {
"dev": "next dev -H 0.0.0.0 -p 3000"
}
}In Express / Node HTTP Server:
// INCORRECT: Defaults to 127.0.0.1 on some operating systems
app.listen(3000);
// CORRECT: Explicitly accepts traffic across all local interfaces
app.listen(3000, '0.0.0.0', () => {
console.log('Server running on http://localhost:3000');
});Solution #2: Fix Docker Port Mapping & Container Bindings
Inside a Docker container, localhost refers to the container itself. If your app inside Docker binds to 127.0.0.1, it refuses any connections originating from outside the container (including your Mac host).
The Incorrect Setup
# docker-compose.yml
services:
web:
build: .
# WRONG: Server inside container only listens to internal container requests
command: node server.js --host 127.0.0.1
ports:
- "3000:3000"The Correct Fix
- Configure the internal application to listen on
0.0.0.0. - Confirm that the port mapping syntax is
HOST_PORT:CONTAINER_PORT.
# docker-compose.yml
services:
web:
build: .
# CORRECT: Listens on all container interfaces
command: node server.js --host 0.0.0.0
ports:
- "3000:3000"Check if your container is actively publishing the port to macOS:
docker compose psThe Ports column must display 0.0.0.0:3000->3000/tcp. If the column is empty, no port was published to your Mac.
Solution #3: Identify & Kill Ghost PIDs
When a terminal tab is closed or a process is suspended with Ctrl+Z, the process remains active in the background, holding onto the port.
Step 1: Find the process locking your port
lsof -nP -iTCP:3000 -sTCP:LISTENTerminal output:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 48291 user 23u IPv4 0x823491823901823 0t0 TCP *:3000 (LISTEN)Step 2: Kill the process
# Terminate by PID
kill -9 48291
# Or kill all processes occupying port 3000 in one command
lsof -ti :3000 | xargs kill -9Once cleared, start your development server again.
Solution #4: Restore macOS Loopback in /etc/hosts
If /etc/hosts was edited by a script or third-party utility, the core loopback entries might be missing or corrupted.
Check the file contents
cat /etc/hostsYour file must contain these standard loopback lines:
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhostRestore missing entries
If 127.0.0.1 localhost is missing:
sudo nano /etc/hostsAdd the standard loopback lines to the top of the file. Press Ctrl+O, hit Enter to save, and Ctrl+X to exit. Then flush your macOS resolver cache:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponderSolution #5: Check macOS Application Firewall
The macOS Application Firewall or third-party tools like LuLu and Little Snitch can block incoming loopback socket connections when new runtime binaries are compiled.
Step 1: Test with firewall temporarily disabled
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate offTest http://localhost:3000 in your browser.
Step 2: Re-enable the firewall and authorize your runtime
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on- Open System Settings > Network > Firewall.
- Click Options....
- Ensure your runtime (Node, Python, Docker) is set to Allow incoming connections.
Solution #6: Python Host Binding Configuration
Python frameworks bind strictly to 127.0.0.1 by default, which can cause connection issues across local container bridges and IPv6 queries.
Flask
# INCORRECT (IPv4 loopback only)
app.run(port=5000)
# CORRECT (Listens on all interfaces)
app.run(host='0.0.0.0', port=5000)Django
# INCORRECT
python manage.py runserver
# CORRECT
python manage.py runserver 0.0.0.0:8000FastAPI / Uvicorn
uvicorn main:app --host 0.0.0.0 --port 8000 --reloadSolution #7: MAMP and Web Server Port Mismatch
MAMP and local Apache installations default to non-standard ports to avoid needing root privileges (< 1024).
- Standard HTTP URL:
http://localhost(Port 80) - MAMP Default URL:
http://localhost:8888
If you enter http://localhost while MAMP is listening on port 8888, the browser will report ERR_CONNECTION_REFUSED. Check MAMP Preferences > Ports to verify your configured port.
The Domino Effect: What Breaks Next?
Once you fix the connection refusal, anticipate the next two roadblocks:
1. The SSL / HTTPS Downgrade Issue
If your application uses OAuth redirects, authentication cookies (SameSite=None; Secure), or modern Web APIs, plain http://localhost will fail.
- Next Step: Use
mkcertto issue locally trusted SSL certificates for your development domains. See how to configure local HTTPS certificates on Mac.
2. Cookie Pollution Across Multiple Projects
If you develop multiple projects on localhost:3000, localhost:3001, and localhost:8080, all projects share the same origin cookie space. Logging into project A will overwrite or invalidate your session in project B.
- Next Step: Move away from shared
localhostURLs and create isolated local domain names (such asproject-a.testandproject-b.test).
The 5-Step Clean Diagnostic Sequence
Whenever a local service refuses to connect, run this exact sequence in Terminal:
# 1. Verify macOS resolves localhost
dscacheutil -q host -a name localhost
# 2. Check if a process is listening on your port
lsof -nP -iTCP:3000 -sTCP:LISTEN
# 3. Test direct IPv4 loopback
curl -I http://127.0.0.1:3000
# 4. Test localhost resolution
curl -I http://localhost:3000
# 5. Flush the local resolver cache
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponderStop Fighting /etc/hosts and Port Collisions Manually
Manually killing ghost PIDs, editing /etc/hosts in nano with sudo, and remembering which random port belongs to which project is tedious and error-prone. A single syntax typo in /etc/hosts can break local domain resolution across your entire system.
ToggleHosts simplifies your local development environment:
- Isolated Project Environments: Toggle custom local domains (
api.client.test,dashboard.client.test) per project in one click. - No Sudo Friction: Update and manage hosts safely without touching raw system files in Terminal.
- Instant Port Routing: Map friendly domain names directly to local ports without cookie conflicts.
Keep your terminal focused on building features, not debugging loopback sockets.
Sources and further reading
- Terminal User Guide (Apple Support)
- localhost and the loopback address (Wikipedia)
- The hosts file explained (Wikipedia)
Frequently Asked Questions
The most common causes are: the server is bound to IPv4 only while macOS resolves IPv6 first, an unseen background process locked the port, Docker bound to container-local loopback, or the /etc/hosts loopback line was deleted.
Run 'lsof -nP -iTCP:PORT -sTCP:LISTEN' (for example: lsof -nP -iTCP:3000 -sTCP:LISTEN) to verify if any process is actively listening on that port.
Technically yes, but localhost resolves through DNS/hosts (often checking IPv6 ::1 first), while 127.0.0.1 is direct IPv4. If 127.0.0.1 works but localhost fails, your server is bound strictly to IPv4 or your hosts file loopback entry is corrupted.
Identify the process with 'lsof -i :PORT', then kill it with 'kill -9 PID', or kill all listeners at once using 'lsof -ti :PORT | xargs kill -9'.
Yes. If the macOS Application Firewall or third-party security software blocks incoming socket connections for your runtime binary (Node, Python, Docker), local connections will fail.
Related Articles
How to Flush DNS Cache on Windows, macOS & Linux (2026)Flushing DNS & cache
How to Flush DNS Cache on Windows, macOS & Linux (2026)
8 min read
Fix EACCES: permission denied on /etc/hosts (2026)Troubleshooting
Fix EACCES: permission denied on /etc/hosts (2026)
3 min read
Localhost Refused to Connect: How to Fix ItTroubleshooting
Localhost Refused to Connect: How to Fix It
4 min read