uvicorn icon indicating copy to clipboard operation
uvicorn copied to clipboard

Support Binding to Multiple Ports

Open bartboy011 opened this issue 5 years ago • 21 comments

Hello there! I'd like to use Uvicorn for an application I'm working on, however, my use-case requires the ability to bind the application to multiple ports. Looking through the source code, this doesn't seem possible, but I'm curious if a workaround has been discussed before, and/or if this is on the roadmap?

bartboy011 avatar Feb 10 '20 18:02 bartboy011

Not currently planned. Out of interest, why is binding to multiple ports necessary/useful here?

lovelydinosaur avatar Feb 10 '20 19:02 lovelydinosaur

Actually now I think about it, that’s not quite true, you can do it using Gunicorn with the Uvicorn worker.

lovelydinosaur avatar Feb 10 '20 19:02 lovelydinosaur

@tomchristie thank you! We'll look into that.

The use case is that the app will be deployed in a kubernetes cluster - the main port uses a TLS connection, but the kubelet doesn't have access to the TLS certs so it can't connect to the primary port to conduct a health check. So, effectively the use-case for a second port binding is to allow for one port to be for web traffic while the other is used for health checks by Kubernetes.

bartboy011 avatar Feb 10 '20 19:02 bartboy011

@tomchristie, adding my use case here (I had to delete my previous comment because I forgot to redact some information).

I am trying to add SSL and HTTPS redirect to a plotly dash app without having to add nginx to the stack. Dash uses flask internally. This is what I could do with hypercorn.

from hypercorn.middleware import AsyncioWSGIMiddleware, HTTPToHTTPSRedirectMiddleware
app_server = HTTPToHTTPSRedirectMiddleware(AsyncioWSGIMiddleware(dash_app.server), "127.0.0.1:443")
$ hypercorn --certfile mycertfile --keyfile mykeyfile --bind 0.0.0.0:443 --insecure-bind 0.0.0.0:80 main:app_server

I attempted to do the same with fastapi+uvicorn.

from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
from fastapi.middleware.wsgi import WSGIMiddleware
app_server = FastAPI()
app_server.mount("/", WSGIMiddleware(dash_app.server))
app_server.add_middleware(HTTPSRedirectMiddleware)
$ uvicorn --ssl-keyfile mykeyfile --ssl-certfile mycertfile --port 443 main:app_server

This won't accept connections on port 80. I haven't been able to find a way to implement my use case with uvicorn. If there is a way to do it, please let me know.

jeet-parekh avatar Sep 26 '20 07:09 jeet-parekh

Adding another use case:

Having a service for which one port is exposed to public internet while another to an internal network.

Something like:

gunicorn -k uvicorn.workers.UvicornWorker -w 1 --bind ip1:port1 --bind ip2:port2 --bind ip3:port3

works.

FlorianLudwig avatar Apr 30 '21 15:04 FlorianLudwig

Another use case, real traffic goes through default 80 whereas prometheus metrics to 8088.

NargiT avatar Nov 09 '21 11:11 NargiT

FYI - I've ended up following this guide: https://fastapi.tiangolo.com/deployment/manually/#hypercorn-with-trio and am no longer using Uvicorn. Hypercorn supports this use case perfectly.

bartboy011 avatar Nov 09 '21 15:11 bartboy011

This would be useful to have with uvicorn directly (in my case to expose prometheus metrics internally within a kubernetes cluster). In my case my app is already customized to uvicorn, but I might look into hypercorn.

MatthewScholefield avatar Nov 12 '21 07:11 MatthewScholefield

This would be useful to have with uvicorn directly (in my case to expose prometheus metrics internally within a kubernetes cluster). In my case my app is already customized to uvicorn, but I might look into hypercorn.

We did some testing internally and for our case performance were low with hypercorn compare to uvicorn. Make sur to do some benckmark before to switch @MatthewScholefield

NargiT avatar Nov 15 '21 07:11 NargiT

Ran into this also. Had to switch to hypercorn and use --insecure-bind and --bind. My use case was exposing prometheus metrics internally within a kubernetes cluster, which had to be on HTTP, so I couldn't use the main HTTPS binding, HTTPS was otherwise required. I hope this feature can be added to uvicorn.

Atheuz avatar Nov 24 '21 11:11 Atheuz

@Atheuz As I wrote above, that's the same as my use case, but I just wanted to add that in my case I found out it's actually a littler cleaner to just start a metrics server independent of fast api just for prometheus metrocs hosting. Then you don't have to worry about concealing the metrics endpoint from public traffic. You can actually do this out of the box with fastapi-prometheus-instrumentor (if you happen to be using this) by just not calling .expose() and instead doing start_http_server()(from prometheus_client) in a fastapi startup handler (and if you want to make it correct, also close it in the shutdown handler).

MatthewScholefield avatar Nov 24 '21 11:11 MatthewScholefield

@MatthewScholefield I'm not concerned about the metrics endpoint being public. I wasn't using fastapi-prometheus-instrumentor (instead using starlette-exporter), and I was having issues with using start_http_server because it didn't capture http request metrics or much of anything other than custom metrics.

I'll keep experimenting to see if I can get uvicorn to work, but hypercorn seems to do what I need for now.

Atheuz avatar Nov 24 '21 12:11 Atheuz

@MatthewScholefield

@Atheuz As I wrote above, that's the same as my use case, but I just wanted to add that in my case I found out it's actually a littler cleaner to just start a metrics server independent of fast api just for prometheus metrocs hosting. Then you don't have to worry about concealing the metrics endpoint from public traffic. You can actually do this out of the box with fastapi-prometheus-instrumentor (if you happen to be using this) by just not calling .expose() and instead doing start_http_server()(from prometheus_client) in a fastapi startup handler (and if you want to make it correct, also close it in the shutdown handler).

Is this present in the doc ? As most of the people we are using starlette-exporter but ready to switch. I like this idea because it allow to split network between public and technical aspect of the app.

NargiT avatar Nov 29 '21 07:11 NargiT

@tomchristie I still want this and I'm wondering how big of a task it is to add additional options to the command line, such as:

--insecure-host=0.0.0.0 --insecure-port=8000

for serving HTTP even if HTTPS is enabled on the main command line options (--host and --port with --ssl-keyfile and --ssl-certfile).

That plus the changes required to uvicorn itself to handle serving HTTPS on the primary host/port and HTTP on the secondary host/port.

That and would you accept a PR for this if I working on implementing it?

Atheuz avatar Jan 12 '22 13:01 Atheuz

I don't think we'd likely want to add those extra command-line flags. Too much of a niche benefit vs the complexity that it adds.

Might be worth looking at if you're able to do this with gunicorn w/ uvicorn workers or not. Otherwise, starting two different processes (one of HTTP and one for HTTPS) is probably an okay approach I guess.

lovelydinosaur avatar Jan 12 '22 13:01 lovelydinosaur

One of the issue using gunicorn is the lack of integration between gunicorn parameters and uviwork workers. Each projects must create it's own worker from uvicorn.workers.UvicornWorker and this becomes a nightmare in an entreprise.

Moreover gunicorn with uvicorn seems to have some serious issue inside kubernetes: https://github.com/encode/uvicorn/issues/1226.

NargiT avatar Jan 12 '22 14:01 NargiT

@tomchristie having two different processes would not be acceptable for all use cases. As an example: Prometheus metrics. If you have two separate processes, then stats would be counted separately for HTTP and HTTPS. If you're just using HTTP to serve scrapes from the Prometheus agent, then the only real contribution would be those scrapes and and not any real traffic, basically rendering it pointless. (Unless you do some screwing around where you make them somehow share metrics, like writing them to a file and giving that to the Prom agent).

Unsure about Gunicorn with a Uvicorn worker.

The solution that @MatthewScholefield mentioned where you have a separate HTTP server in the same process for only metrics would probably be OK though, but this is only OK in the use case for Prometheus. There are probably other use cases where it would be much nicer to just have the server expose on both HTTP and HTTPS.

Another alternative is to use Hypercorn because you can specify --insecure-bind there, but Uvicorn seems faster and more broadly used.

But OK, thank you for responding and giving your input on this, I wasn't sure if this was ever going to progress and at least having some certainty that there probably won't be anything done about this, helps to decide future steps.

Atheuz avatar Jan 12 '22 15:01 Atheuz

Moreover gunicorn with uvicorn seems to have some serious issue inside kubernetes: https://github.com/encode/uvicorn/issues/1226.

It would be great to see someone properly take on an ASGI worker built-in to Gunicorn, rather than Uvicorn's somewhat spotty support.

Another alternative is to use Hypercorn because you can specify --insecure-bind there

Yup - hypercorn is really nicely put together, and I prefer some of the design decisions made there vs. uvicorn.

Going to close this off for now, since I think uvicorn ought to be in feature-pause right now, and just focus on making sure it's doing a decent job with it's current feature set.

lovelydinosaur avatar Feb 03 '22 14:02 lovelydinosaur

I am sad that this was closed, an obvious use case is anything that supports a protocol other than HTTP(S) and still wants to export Prometheus metrics.

didier-viboo avatar May 16 '25 13:05 didier-viboo

Actually the comment below is inaccurate; that method works but swallows exceptions raised by the servers, making it difficult to know when something has gone wrong. To work around this, use asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) instead of await asyncio.Future() so that uncaught exceptions cause the process to exit.

Original comment I'm also running up against this. In my case, I want to bind two separate web-apps which share some internal state to different ports. One is a TLS port served to the world and the other a non-TLS port serving debug information locally. I'm trying to do this:

async def serve_https():
    config = uvicorn.Config(app1, host="0.0.0.0", port=443, ssl_keyfile=..., ssl_certfile=..., ssl_ca_certs=..., ssl_cert_reqs=ssl.CERT_REQUIRED)
    server = uvicorn.Server(config)
    server.config.setup_event_loop()
    await server.serve()

async def serve_http():
    config = uvicorn.Config(app2, host="127.0.0.1", port=80)
    server = uvicorn.Server(config)
    server.config.setup_event_loop()
    await server.serve()

async def run():
    tasks = [asyncio.create_task(serve_http()), asyncio.create_task(serve_https())]
    await asyncio.Future()

But this only seems to start one of the servers (the second one in the list).

tomkcook avatar Jul 11 '25 15:07 tomkcook

Hi, we are facing the same issue @bartboy011 reported. For an enterprise use case we would like to use fastAPI with uvicorn but the application will be deployed in a kubernetes environment with different ports for application traffic and control traffic (basically kubernetes liveness and readiness probes), so this is an important limitation. Any advancement with this request would be much appreciated.