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.
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
443with 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.
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.
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.
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.ProxyFixsourl_fortrustsX-Forwarded-Proto. - Configure the proxy trust count carefully instead of guessing.
- Set
PREFERRED_URL_SCHEMEfor URL generation outside a request context. - Avoid scattering
_scheme="https"across the codebase when the real issue is proxy configuration.

