Reputation: 365
I have a small, test FastAPI web application that is serving a simple HTML page that requires a css style sheet located in the static folder. It is installed on a Linode server (Ubuntu 20.04 LTS), nginx, gunicorn, uvicorn workers, and supervisorctl. I have added a certificate using certbot.
The application works fine in http but does not access the static files in https. When accessed in http all static-based features work but when accessed with https it lacks all styling from css stylesheet. I need to get this working so I can load a much more complex app that needs css and other static folder-stored features.
The file structure is:
/home/<user_name>/application
- main.py
- static
|_ css
|_ bootstrap
- templates
|_ index.html
main.py:
import fastapi
import uvicorn
from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
api = fastapi.FastAPI()
api.mount('/static', StaticFiles(directory='static'), name='static')
templates = Jinja2Templates(directory="templates")
@api.get('/')
@api.get('/index', response_class=HTMLResponse)
def index(request: Request):
message = None
return templates.TemplateResponse("index.html", {"request": request,
'message': message})
if __name__ == '__main__':
uvicorn.run(api, port=8000, host='127.0.0.1')
nginx is at /etc/nginx/sites-enabled/<my_url>.nginx
server {
listen 80;
server_name www.<my_url>.com <my_url>.com;
server_tokens off;
charset utf-8;
location / {
try_files $uri @yourapplication;
}
location /static {
gzip on;
gzip_buffers 8 256k;
alias /home/<user_name>/application/static;
expires 365d;
}
location @yourapplication {
gzip on;
gzip_buffers 8 256k;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Protocol $scheme;
}
}
server {
listen 443 ssl;
server_name www.<my_url>.com;
ssl_certificate /etc/letsencrypt/live/<my_url>.com/fullchain.pem; # mana>
ssl_certificate_key /etc/letsencrypt/live/<my_url>.com/privkey.pem; # ma>
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
location / {
try_files $uri @yourapplication;
}
location /static {
gzip on;
gzip_buffers 8 256k;
alias /home/<user_name>/application/static;
expires 365d;
}
location @yourapplication {
gzip on;
gzip_buffers 8 256k;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Protocol $scheme;
}
}
and am serving using supervisor script:
[program:api]
directory=/home/<user_name>/application
command=gunicorn -b 127.0.0.1:8000 -w 4 -k uvicorn.workers.UvicornWorker main:api
environmentenvironment=PYTHONPATH=1
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stderr_logfile=/var/log/app/app.err.log
stdout_logfile=/var/log/app/app.out.log
The css stylesheet is called in the html using url_for like this:
<link href="{{ url_for('static', path='/css/ATB_style.css') }}" rel="stylesheet">
I have tried a whole host of modifications to the location /static block in nginx including:
I have loaded this server twice, once letting certbot modify the nginx file the second, and current configuration, where I did it manually. I am at a complete loss on what to do.
Upvotes: 8
Views: 6184
Reputation: 1
A bit late, but I encountered this issue today and fixed it. I'm using nginx as a reverse proxy that queries my server. I originally followed Brad Allen's answer, but while that worked on my production server it stopped running on localhost (as that is sending HTTP requests, not HTTPS). I saw in the docs that I can update the response headers instead of adding the tag into my HTML: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy.
I changed my nginx config on my production server to add a header to the response before it's sent back to the user.
add_header Content-Security-Policy upgrade-insecure-requests;
This cleared up my issue.
I tossed up a few options and settled on this in the end, but if it leads to issues then I'll try one using an environment variable "HTTPS_REQUESTS" which I access when generating my HTML with Jinja2.
Upvotes: 0
Reputation: 128
In case of using upstream
, proxy_pass
should also be set as follow:
http {
upstream myapp {
server application_container:4001;
}
server {
listen 80;
server_name localhost;
location / {
...
}
location /myapp/ {
proxy_pass http://myapp;
...
}
location /static/ {
proxy_pass http://myapp;
alias ...
}
}
Upvotes: 0
Reputation: 365
Thanks to @AdramKhan for the comment that provided a work-around for an important demo. I added a meta line to my html page to allow access to the css stylesheet with https:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
This was only a work-around as it is dealing with a hard-coded HTTP request in the code somewhere per this: How can I allow Mixed contents (http with https) using content-security-policy meta tag?
Solving the root cause was changing how static content was called in the head of html files. The problems (there were three) were with references like this where there was a jinja2 url_for instead of a direct href:
<link href="{{ url_for('static', path='/css/MH_style.css') }}" rel="stylesheet">
When replaced with a reference of this format, using href:
<link rel="stylesheet" href="/static/css/MH_style.css"/>
Everything worked without the Content-Security-Policy meta.
Upvotes: 9
Reputation: 3380
I'd think you want the server to handle it. If you just setup a separate block on port 80 to convert all requests to 443 (HTTPS) permanently, you'd be good:
server {
listen 80;
server_name yourserver.com;
return 301 https://yourserver.com$request_uri;
}
server {
listen 443 ssl http2;
server_name yourserver.com;
...
}```
Upvotes: 0