1

Everything was running fine until I switched the application to use https. All the links that the url_for function generates in the templates now look like this https://ibb.co/N3cJ9V4

Problem:

Mixed Content: The page at 'https://team-mate.app/' was loaded over HTTPS, but requested an insecure stylesheet 'http://team-mate.app/static/css/materialize.min.css'. This request has been blocked; the content must be served over HTTPS.

I've seen similar problems on stackoverflow and tried every possible option, but nothing worked.

I tried the advice I found and ran Uvicorn through --proxy-headers

Dockerfile ....

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]

docker-compose.yaml

....

command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers"

But nothing has changed. The problem remains. Maybe I misunderstood the advice or the nginx config needs to be edited somehow.

The second way I used

from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)

But got cyclic redirect HTTP/1.0" 307 Temporary Redirect for all my urls`

Also tried this No effect

My current configs

nginx-config

server {
    listen 80;
    server_name team-mate.app;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}
server {
    listen 443 ssl;
    server_name team-mate.app;
    server_tokens off;

    location /static/ {
        gzip            on;
        gzip_buffers    8 256k;
        root /app;
    }

    ssl_certificate /etc/letsencrypt/live/team-mate.app/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/team-mate.app/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://web:8000;
        proxy_set_header    Host                $host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    }

    location /favicon.ico {
        access_log off;
        log_not_found off;
    }

}

docker-compose.yaml

version: '3.9'

services:
  web:
    env_file: .env
    build: .
    command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload"
    volumes:
      - .:/app
    ports:
      - 8000:8000
    depends_on:
      - db
      - redis
  db:
    image: postgres:11
    volumes:
      - postgres_data:/var/lib/postgresql/data
    expose:
      - 5432
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_DB=${DB_NAME}
  redis:
    image: redis:6-alpine
    volumes:
      - redis_data:/data
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    volumes:
      - ./nginx_config.conf:/etc/nginx/conf.d/default.conf
      - ./data/nginx:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - web
  certbot:
    image: certbot/certbot
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  postgres_data:
  redis_data:

main.py

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

views.py

@router.get('/', response_class=HTMLResponse)
async def main_page(request: Request,
                    user: User = Depends(UserService.get_authenticated_user_id)
                    ):
    return templates.TemplateResponse('base.html',
                                      context={
                                          'request': request,
                                          'user': user,
                                      }
                                      )

html

  <link type="text/css" href="{{ url_for('static', path='/css/materialize.min.css') }}" rel="stylesheet">
  <link type="text/css" href="{{ url_for('static', path='/css/custom.css') }}" rel="stylesheet">

p.s.

I followed MatsLindh's advice and added the proxy_set_header X-Forwarded-Proto $scheme parameter to the nginx configuration

And added --forwarded-allow-ips="*" to docker-compose file as

command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips="*""

And it made a bit of a difference - the debugger stopped writing Mixed-content but returns 404 error for static files

404

upd2

In the end, after many attempts to solve the problem, I managed to convert the application to work with https. Now the function url_for e.g. href="{{ url_for('user_profile', pk=member.id) }}" is correctly executed and redirects to the correct URL. Also, the warning in the browser that the connection is not secure is gone.

The trick was to use --proxy-headers --forwarded-allow-ips inside docker-compose

command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips="*""

and X-Forwarded-Proto in nginx config

proxy_set_header    X-Forwarded-Proto   $scheme;

There is one problem left to solve. I still get 404 for the static folder. Therefore, the styles don't work.

My current nginx-config

server {
    listen 80;
    server_name team-mate.app;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}
server {
    listen 443 ssl;
    server_name team-mate.app;
    server_tokens off;

    location /static/ {
        gzip            on;
        gzip_buffers    8 256k;
        alias /app/static/;
    }

    ssl_certificate /etc/letsencrypt/live/team-mate.app/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/team-mate.app/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://web:8000;
        proxy_set_header    Host                $host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
    }

    location /favicon.ico {
        access_log off;
        log_not_found off;
    }

}

and docker-compose.yaml

version: '3.9'

services:
  web:
    env_file: .env
    build: .
    command: sh -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips="*""
    volumes:
      - .:/app
    ports:
      - 8000:8000
    depends_on:
      - db
      - redis
  db:
    image: postgres:11
    volumes:
      - postgres_data:/var/lib/postgresql/data
    expose:
      - 5432
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_DB=${DB_NAME}
  redis:
    image: redis:6-alpine
    volumes:
      - redis_data:/data
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    volumes:
      - ./nginx_config.conf:/etc/nginx/conf.d/default.conf
      - ./data/nginx:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - web
  certbot:
    image: certbot/certbot
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  postgres_data:
  redis_data:

if I explicitly specify the path to the statics in the templates

<link rel="stylesheet" href="https://team-mate.app/static/css/custom.css'">

it still returns a 404.

Static file mount

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

Project structure

wplay
├── api
│   ├── endpoints
│   │   └── __pycache__
│   ├── __pycache__
│   └── services
│       └── __pycache__
├── data
│   ├── certbot
│   │   ├── conf
│   │   │   └── renewal-hooks
│   │   │       ├── deploy
│   │   │       ├── post
│   │   │       └── pre
│   │   └── www
│   └── nginx
├── endpoints
│   └── __pycache__
├── helpers
│   └── __pycache__
├── migrations
│   ├── __pycache__
│   └── versions
│       └── __pycache__
├── models
│   └── __pycache__
├── __pycache__
├── schemas
│   └── __pycache__
├── services
│   └── __pycache__
├── sessions
│   ├── core
│   │   └── __pycache__
│   └── __pycache__
├── static
│   ├── css
│   ├── fonts
│   │   └── roboto
│   ├── img
│   └── js
└── templates

UPD

File response works correct

@router.get("/download")
async def download_file():
    file_path = "static/img/6517.svg"
    return FileResponse(file_path, media_type="image/svg+xml")

UPD 20.02.2023

I left the files: nginx-config, docker-compose, Dockerfile unchanged and ran a simple application

main.py

from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates


from db import database


app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

@app.get("/")
async def home(request: Request):
    return templates.TemplateResponse('base.html',
                                      context={'request': request,
                                      }
                                      )

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ToDo App</title>
  <link type="text/css" href="{{ url_for('static', path='/css/styles.css') }}" rel="stylesheet">

</head>
<body>
    <main>
        <h1>ToDo App</h1>
        <br>
        {% block content %}

        {% endblock content %}
    </main>
</body>
</html>

And got the same result 404 for https://team-mate.app/static/css/styles.css

Jekson
  • 2,892
  • 8
  • 44
  • 79
  • 1
    Have you seen https://stackoverflow.com/questions/66051867/starlettes-url-for-doesnt-create-links-with-https-scheme-behind-nginx-via-uvi ? You'll need to supply `X-Forwarded-Proto` and tell uvicorn to trust the reverse proxy. – MatsLindh Feb 08 '23 at 23:24
  • @MatsLindh I completed the question with the results of the experiment in the link you provided – Jekson Feb 09 '23 at 22:14
  • @Jekson Can you please confirm whether or not the error on loading static files is related to [this answer](https://stackoverflow.com/a/74498663/17865804) and [this answer](https://stackoverflow.com/a/73113792/17865804)? – Chris Feb 15 '23 at 06:25
  • @Chris Thanks for the links. I have read it and it seems to me that my statics connection is correct. I have updated my question with information on how the statics are connected and the structure of the project – Jekson Feb 15 '23 at 11:25
  • @Chris Yes it works. I updated my question with code samle. You can check the reult here https://team-mate.app/download – Jekson Feb 18 '23 at 10:45
  • @Chris updated my question with code samle – Jekson Feb 20 '23 at 16:36

0 Answers0