19

I'm trying to run a fastapi app with SSL.

I am running the app with uvicorn.

I can run the server on port 80 with HTTP,

if __name__ == '__main__':
    uvicorn.run("main:app", port=80, host='0.0.0.0', reload = True, reload_dirs = ["html_files"])

To run the port with HTTPS, I do the following,

if __name__ == '__main__':
    uvicorn.run("main:app", port=443, host='0.0.0.0', reload = True, reload_dirs = ["html_files"], ssl_keyfile="/etc/letsencrypt/live/my_domain/privkey.pem", ssl_certfile="/etc/letsencrypt/live/my_domain/fullchain.pem")

How can I run both or simply integrate https redirect?

N.B: This is a setup on a server where I don't want to use nginx, I know how to use nginx to implement https redirect.

0

3 Answers 3

13

You could use the HTTPSRedirectMiddleware. This would enforce "that all incoming requests must either be https or wss. Any incoming requests to http or ws will be redirected to the secure scheme instead`.

Once both the apps below are running, the URL http://127.0.0.1, for instance, will be redirected to https://127.0.0.1.

If you wish disabling the Swagger UI and ReDoc API autodcos in production, you could instead use the following line in the examples below:

app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)

If you would like having the https_redirect app starting up automatically when running the main app, you might do that using the subprocess module (inside main.py), as shown below:

if __name__ == "__main__":
    import subprocess
    subprocess.Popen(['python', '-m', 'https_redirect']) 
    uvicorn.run(
        "main:app",
        ...
    )

Working Example 1

https_redirect.py

from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
import uvicorn

app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=80)

main.py

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/")
async def main():
    return {"message": "Hello World"}


if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=443,
        ssl_keyfile="./key.pem",
        ssl_certfile="./cert.pem",
    )

Note that, as shown in the implementation of HTTPSRedirectMiddleware, the redirection should only take place when the apps are listening on ports 80 and 443, respectively. If a different port is used (e.g., 8000 for both the apps—that should actually be a single main app, having the HTTPSRedirectMiddleware mounted as well), even though the HTTPSRedirectMiddleware, as shown in its implementation, would change the scheme to https and keep the same port number used in the original request URL when returning the RedirectResponse, a redirection would not actually take place, as you can't really have both HTTP and HTTPS apps listening on the same port number (when the SSL files are included in the app, it should only be listening for SSL connections). In that case, you should use two different port numbers, as demonstrated in the example earlier. If, however, the ports one has chosen differ from 80 and 443, a customized middleware could be used, as shown in the following example (the apps below, for demo purposes, are listening on ports 8000 and 8443, respectively).

Working Example 2

https_redirect.py

from fastapi import FastAPI
from starlette.datastructures import URL
from starlette.responses import RedirectResponse
from starlette.types import ASGIApp, Receive, Scope, Send
import uvicorn


class HTTPSRedirectMiddleware:
    def __init__(self, app: ASGIApp) -> None:
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] in ("http", "websocket") and scope["scheme"] in ("http", "ws"):
            url = URL(scope=scope)
            redirect_scheme = {"http": "https", "ws": "wss"}[url.scheme]
            url = url.replace(scheme=redirect_scheme, port=8443)
            response = RedirectResponse(url, status_code=307)
            await response(scope, receive, send)
        else:
            await self.app(scope, receive, send)


app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)
 

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)  # HTTP port set to 8000

main.py

# same code implementation here as in "Working Example 1"
# ...


if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8443, # HTTPS port set to 8443
        ssl_keyfile="./key.pem",
        ssl_certfile="./cert.pem",
    )

Configuring HTTPS redirection in Nginx reverse proxy server

Alternative solutions include using a reverse proxy server, such as Nginx, and letting it catch all port 80 (HTTP) requests and redirecting them to port 443 (HTTPS)—on which the main app will be listening for SSL connections only:

server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

In the example above, the server_name is set to _, which matches any hostname used. It will return 301 redirect to the HTTPS version of whatever URI was requested.

You could also redirect only specific sites, which is convenient when you have multiple apps/sites that not all of them should be forced to use HTTPS connections. Additionally, instead of configuring the SSL certificate and key in your app, one may set this configuration on the reverse proxy server, including the app/site that needs to listen on port 443 for SSL connection:

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate /path/to/your/cert.pem;
    ssl_certificate_key /path/to/your/key.pem;
    
    location / {
        proxy_pass http://localhost:8000;  # if you have the app running on port 8000
    }
}

For further reverse proxy configurations, you may have a look at the bottom of this answer.

Sign up to request clarification or add additional context in comments.

5 Comments

My ssl certificate is not in local machine, it's on heroku. But tried this to add and test in my local machine, which creates an error. Any suggestion would be appreciated.
Even with HTTPSRedirectMiddleware installed, I'm getting The connection was reset in Firefox and curl: (52) Empty reply from server with curl for HTTP requests. This is same behaviour as without the middleware. It makes sense to me, because Uvicorn says it is only listening for HTTPS on the port. How's it supposed to work?
Not working for me :/
@ArnoV I am afraid that "Not working..." is very abstract and vague, without stating what the case is, as well as any debugging details. I would also suggest you have a look at related answers here and here, which might help with the issue you are facing.
What I mean by "not working" is that when my user types "example.com" in the search bar they get a negative answer and they have to explicitly type "HTTPS://example.com"
13

Run a subprocess to return a redirect response from one port to another.

main.py:

if __name__ == '__main__':
    Popen(['python', '-m', 'https_redirect'])  # Add this
    uvicorn.run(
        'main:app', port=443, host='0.0.0.0',
        reload=True, reload_dirs=['html_files'],
        ssl_keyfile='/path/to/certificate-key.pem',
        ssl_certfile='/path/to/certificate.pem')

https_redirect.py:

import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import RedirectResponse

app = FastAPI()


@app.route('/{_:path}')
async def https_redirect(request: Request):
    return RedirectResponse(request.url.replace(scheme='https'))

if __name__ == '__main__':
    uvicorn.run('https_redirect:app', port=80, host='0.0.0.0')

Comments

-1

I think you can redirect all traffic from port 80 to port 443 on the OS level using. For example, on Ubuntu, by run in the terminal:

sudo iptables -t nat -L
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 443 # 80 -> 443

1 Comment

While technically you can do port-level redirects, you will break HTTP applications. HTTP and HTTPS use different schemes and protocols. This answer should be deleted as bad advice for the question asked.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.