There is a very strange behavior in macOS Ventura 13.1 (and possibly earlier macOS as well), where the macOS Safari browser and the Google Chrome browser cannot access a Python Flask web development server on port 5000. The URL http://localhost:5000 either shows as completely blank, or responds with a 403 Forbidden.
Context
In short, Flask is a lightweight web application framework for Python. A typical Flask webapp is run in development mode as
flask --debug run
By default, this will start a Flask development server on port 5000:
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
From the command line, subsequently, curl can be used to reach the server
curl "http://localhost:5000/"
In my case, it responds with
Hello world
Note that localhost and 127.0.0.1 are, in typical cases, equivalent to each other. Both of them are pointing to the local machine.
Problem
If using the Safari browser on the same machine, though, and hitting http://localhost:5000, this will display a completely blank page. The development server itself will not show that a GET request was made, which suggests that the request never made it to the flask application at all.
If using the Chrome browser to hit http://localhost:5000, it will display a 403 Forbidden error instead of a blank page:
Access to localhost was denied You don't have authorization to view this page. HTTP ERROR 403
And again, the Flask dev server itself does not show a successful GET request being made.
As a final symptom, if instead http://127.0.0.1:5000 was used, a GET will be successfully received by the Flask dev server, and the server will respond to it normally.
Solution
- On macOS Ventura, go to Settings -> General -> AirDrop & Handoff and turn off AirPlay Receiver.
- Alternatively, instruct Flask to use a different port for its dev server other than 5000 and access that port instead:
flask --debug run -p 8000
- Alternatively, always interact with Flask on the IP literal 127.0.0.1:5000 rather than the localhost DNS name. This is what the Flask instructions say to do anyway.
Discussion
It turns out there is a port conflict, of sorts. On macOS Ventura (and possibly Monterey as well), the OS’s built-in AirPlay Receiver is also listening on TCP port 5000 for some purpose. Running
lsof -i -P
... ControlCe 423 yliu 7u IPv4 0t0 TCP *:5000 (LISTEN) ControlCe 423 yliu 8u IPv6 0t0 TCP *:5000 (LISTEN) ... Python 1374 yliu 5u IPv4 0t0 TCP localhost:5000 (LISTEN)
we can see that ControlCenter (really AirPlay Receiver) is listening on TCP *:5000 and the Python Flask app is listening on localhost:5000.
In fact, if the Flask server is turned off, and a curl -i command (to show response headers) is issued to localhost:5000, this is the response:
curl -i "http://localhost:5000/"
HTTP/1.1 404 Not Found Content-Length: 0 Server: AirTunes/670.6.2
I’m not sure why this situation isn’t causing a port conflict, throwing a socket error for “address already in use”, which would block the Python app from starting at all. Similarly, I don’t understand why it doesn’t affect curl when run on the command line, which still successfully reaches the Flask server.
Nevertheless, changing Flask’s socket to an unoccupied port such as 8000, or turning off the offending macOS component AirPlay Receiver that is using the port, addresses the conflict. Now both Safari and Chrome will be able send requests to the actual Flask server, rather than the AirTunes server also residing on port 5000.