Localhost refused to connect on your phone: six checks
When your phone can't reach your dev server, six checks find why: the address, what it listens on, the network, the firewall, host rules and https.
Updated
On a phone, “localhost refused to connect” means the phone looked for a server on itself, because there localhost is the phone, not your computer. Open your computer's network address instead, and if that fails too, these six checks find the cause, in the order things usually go wrong.
This is the list of fixes. For a walk-through of the two ways in, over Wi-Fi or with a link, see how to open localhost on your phone.
Read the error first
What the phone says tells you which checks to start with. These are Chrome's words; Safari and Firefox put it differently, but the causes are the same.
| What the phone shows | What it means | Start with |
|---|---|---|
| “refused to connect”, ERR_CONNECTION_REFUSED | It reached an address, and nothing there took the connection | The address, then what the server listens on |
| “took too long to respond”, ERR_CONNECTION_TIMED_OUT | Nothing answered: something dropped the request on the way | The network, then the firewall |
| ERR_ADDRESS_UNREACHABLE | The phone has no route to that address | The network |
| A page saying the host isn't allowed, or one that ignores taps | The dev server answered, and turned the address away | Host rules |
| The page works, the camera or location doesn't | The page isn't https | Features that need https |
1. Check the address
Not localhost, and not 127.0.0.1: on the phone, both mean the phone. Type your computer's address on the network, with http:// and the port, like http://192.168.1.20:5173. Vite, Astro and Next.js print it as the Network address when they start, and the how-to shows how to find it otherwise.
Check the port, too. Vite's docs warn: “Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on.” A second project left running moves the next one up a port.
2. Check what the dev server listens on
This is the usual cause. Most dev servers listen on 127.0.0.1 only, so the computer answers itself and refuses everyone else. Django's docs say it plainly: “Note that the default IP address, 127.0.0.1, is not accessible from other machines on your network.” Start the dev server with its network flag:
| Dev server | Port | Listen on your network |
|---|---|---|
| Vite (Vue, React, Svelte) | 5173 | npm run dev -- --host |
| Next.js | 3000 | Already does: next dev listens on 0.0.0.0 |
| Angular | 4200 | ng serve --host 0.0.0.0 |
| Astro | 4321 | npm run dev -- --host |
| Nuxt | 3000 | nuxt dev --host 0.0.0.0 |
| Django | 8000 | python manage.py runserver 0:8000 |
| Rails | 3000 | bin/rails server -b 0.0.0.0 |
| Laravel | 8000 | php artisan serve --host=0.0.0.0 |
| Flask | 5000 | flask run --host=0.0.0.0 |
| Hugo | 1313 | hugo server --bind 0.0.0.0 |
Not sure it took? Ask the computer what is listening, with the dev server running:
# macOS and Linuxlsof -nP -iTCP:5173 -sTCP:LISTEN# Windowsnetstat -ano | findstr :5173127.0.0.1:5173 or [::1]:5173 means this computer only. *:5173, 0.0.0.0:5173 or your network address means the phone can reach it. A server listening on the network is open to everyone on that network, so keep debug consoles off on Wi-Fi you share.
3. Check the network
- Both on the same Wi-Fi: not a guest network, and the phone not quietly on mobile data.
- No VPN on either device. A VPN can send the phone's request for a local address out through the VPN, where nothing answers.
- Office, hotel and café networks often stop devices from reaching each other. Nothing on your computer changes that: use a link instead.
4. Check the firewall
- macOS: in System Settings, under Network, then Firewall. With “Block all incoming connections” on, nothing gets in; otherwise allow Node, Python or Ruby when macOS asks.
- Windows: when the dev server first listens, Windows Defender Firewall asks. Allow it on private networks, and check that your Wi-Fi is set as a private network, not a public one.
- Linux with ufw:
sudo ufw allow 5173/tcp.
5. Check the dev server's host rules
If the page loads but stays blank, ignores taps or says the host isn't allowed, the dev server answered and turned the address down. Most guard against addresses they don't know, and a tunnel's address trips the same rules, for reasons of their own.
- Vite: “localhost and domains under .localhost and all IP addresses are allowed by default.” So a network address passes (share a Vite dev server).
- Next.js: since 16.2.0, the page arrives but its hot reload is refused, so edits never show, with “Cross-origin access to Next.js dev resources is blocked by default for safety.” in the terminal. Add the address to allowedDevOrigins; the versions are in share a Next.js dev server.
- Django: with
DEBUGon andALLOWED_HOSTSempty, it accepts only['.localhost', '127.0.0.1', '[::1]']. To anything else it saysInvalid HTTP_HOST header: '192.168.1.20:8000'. You may need to add '192.168.1.20' to ALLOWED_HOSTS.Add the address insettings.py, as below. Through a link, Django's forms need one setting more (share a Django dev server). - Rails, in development, allows any IPv4 or IPv6 address, and localhost, so a network address passes. A link's name doesn't (share a Rails dev server).
ALLOWED_HOSTS = ["192.168.1.20"]6. Check features that need https
If the page works but the camera, location or offline mode doesn't, the address is the problem, not your code. Browsers allow the camera and microphone (getUserMedia), geolocation, service workers, the clipboard, Web Share and notifications only in a secure context: an https page, or localhost, which counts as one, as do the addresses 127.0.0.0/8 and ::1/128. A page at http://192.168.1.20 is neither.
On Android, a USB cable gets round it. With USB debugging on, adb reverse tcp:5173 tcp:5173 makes localhost:5173 on the phone reach the same port on your computer, and localhost counts as secure. Or use an https link.
Or skip the network
A public https link sidesteps all six checks at once. ouicu connects to the dev server on your computer, gives it a link, and draws the link as a QR code in your terminal:
ouicu share 5173Scan the code with the phone's camera. It opens on Wi-Fi or mobile data, over https, and your dev server sees each request as coming to localhost, so host rules like Vite's and Next.js's pass without a change. On Free, the phone shows a short notice from ouicu first, and a share runs for up to 2 hours. The code is on the dashboard too: see QR codes.
Questions
Why does localhost work on my computer but not my phone?
Every device calls itself localhost. On the computer that is where the dev server runs; on the phone there is no server, so the browser finds nothing.
What does ERR_CONNECTION_REFUSED mean on a phone?
The phone reached an address and nothing there accepted the connection: the wrong address or port, or a dev server listening on localhost only. Start with checks one and two.
Can I test on my phone over USB?
On Android, yes, with adb reverse, as in check six. The phone then opens the site at localhost, as you do.
Sources
Prices, defaults and quotes about other products, and the day each was last checked at its source.
- error_page_strings.grdp, Chromium source. Checked .
- net_error_list.h, Chromium source. Checked .
- Server Options, Vite docs. Checked .
- django-admin and manage.py: runserver, Django docs. Checked .
- next CLI, Next.js docs. Checked .
- ng serve, Angular docs. Checked .
- CLI Commands, Astro docs. Checked .
- Nuxt Configuration, Nuxt docs. Checked .
- nuxt dev, Nuxt docs. Checked .
- The Rails Command Line, Rails Guides. Checked .
- ServeCommand.php, Laravel on GitHub. Checked .
- Quickstart, Flask docs. Checked .
- hugo server, Hugo docs. Checked .
- next: versions, npm. Checked .
- block-cross-site-dev.ts, Next.js on GitHub. Checked .
- Settings, Django docs. Checked .
- django/http/request.py, Django on GitHub. Checked .
- Configuring Rails Applications, Rails Guides. Checked .
- Features restricted to secure contexts, MDN. Checked .
- Secure contexts, MDN. Checked .
- adb: commandline.cpp (adb reverse), Android source. Checked .