Flask
AWS
SSL
url_for
HTTPS

Make Flask's url_for use the 'https' scheme in an AWS load balancer without messing with SSLify

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When Flask runs behind an AWS load balancer, TLS often ends at the balancer and the app receives plain HTTP from the proxy. If Flask does not trust the forwarded headers, url_for can generate http links even though the browser reached the site over HTTPS.

Why url_for Picks the Wrong Scheme

url_for builds URLs from request context. Behind a reverse proxy, Flask only sees what the upstream server tells it unless you explicitly adapt the application to proxy headers.

In AWS, an Application Load Balancer typically forwards the original scheme in the X-Forwarded-Proto header. Without middleware, Flask may ignore that value and assume the request scheme is plain HTTP. The result shows up in redirects, emails, and absolute URLs generated with _external=True.

Trust the Proxy With ProxyFix

The standard fix is Werkzeug's ProxyFix middleware. It tells Flask how many proxy layers to trust for headers such as X-Forwarded-Proto and X-Forwarded-Host.

python
1from flask import Flask, url_for
2from werkzeug.middleware.proxy_fix import ProxyFix
3
4app = Flask(__name__)
5
6app.wsgi_app = ProxyFix(
7    app.wsgi_app,
8    x_for=1,
9    x_proto=1,
10    x_host=1,
11)
12
13@app.route("/")
14def index():
15    return {
16        "self": url_for("index", _external=True),
17        "login": url_for("login", _external=True),
18    }
19
20@app.route("/login")
21def login():
22    return "login page"

With that middleware in place, Flask trusts one proxy hop and reads the forwarded protocol correctly. If the incoming request originally used HTTPS, _external=True URLs now use the https scheme.

Configure the AWS Side Correctly

ProxyFix only helps if the proxy actually sends the right headers. For an AWS Application Load Balancer, the usual setup is:

  • Listener on port 443 with a valid certificate.
  • Target group forwarding traffic to your Flask service on HTTP.
  • Default forwarding of X-Forwarded-Proto, X-Forwarded-For, and host-related headers.

If you run behind more than one proxy layer, adjust the ProxyFix counts carefully. Setting x_proto=1 means "trust exactly one upstream proxy for protocol information." If you guess too high, you open yourself to header spoofing from untrusted sources.

Set PREFERRED_URL_SCHEME for Non-Request Contexts

Sometimes you call url_for outside an active request, such as inside a CLI task or an email job. In that case, Flask has no request scheme to inspect, so PREFERRED_URL_SCHEME becomes relevant.

python
1app.config["SERVER_NAME"] = "example.com"
2app.config["PREFERRED_URL_SCHEME"] = "https"
3
4with app.app_context():
5    print(url_for("login", _external=True))

This does not replace ProxyFix for normal web requests. It covers cases where no live request exists and Flask must fall back to configuration values.

Redirects and Session Cookies

Once Flask understands the original scheme, related features behave more predictably. Redirects built with redirect(url_for(...)) stop downgrading to HTTP, and secure cookies can be enabled without confusion.

python
1app.config.update(
2    SESSION_COOKIE_SECURE=True,
3    REMEMBER_COOKIE_SECURE=True,
4)

Those settings tell browsers to send the cookies only over HTTPS. They are not the mechanism that fixes url_for, but they belong in the same deployment conversation because a proxy-terminated HTTPS setup should usually mark cookies as secure.

Why Not Force _scheme="https" Everywhere

You can pass _scheme="https" to url_for, but that is a narrow workaround, not a system fix.

python
url_for("login", _external=True, _scheme="https")

Doing this in scattered call sites creates maintenance debt. It also misses places where Flask itself builds URLs during redirects or other framework-level behavior. Fix the request context once, then let the framework generate correct URLs naturally.

Common Pitfalls

The most common mistake is enabling ProxyFix with the wrong proxy count. If your app trusts more hops than actually exist, malicious clients may be able to inject fake forwarding headers.

Another issue is assuming PREFERRED_URL_SCHEME affects ordinary in-request calls. It does not override a real request context; it mainly helps when generating URLs outside a request.

Developers also sometimes verify only relative URLs. Relative links hide the problem because they do not contain a scheme. Check _external=True output or redirect responses to confirm the app is really producing HTTPS.

Finally, do not add separate HTTPS-forcing middleware just to patch around URL generation. In an AWS load balancer setup, understanding and trusting the forwarded headers is the cleaner fix.

Summary

  • Behind AWS load balancers, Flask needs proxy header awareness to generate HTTPS URLs.
  • Use werkzeug.middleware.proxy_fix.ProxyFix so url_for trusts X-Forwarded-Proto.
  • Configure the proxy trust count carefully instead of guessing.
  • Set PREFERRED_URL_SCHEME for URL generation outside a request context.
  • Avoid scattering _scheme="https" across the codebase when the real issue is proxy configuration.

Course illustration
Course illustration

All Rights Reserved.