Reverse proxy + OctoberCMS Docker v2

Good afternoon. I use my own docker images in production. It worked until the number of containers increased and it was necessary to install a reverse proxy. Over http, it works without errors. But if you install an ssl certificate and redirect 301 https, an error appears: “POST /backend/backend/auth/migrate HTTP/1.1” 403 333 “https://example.com/backend/backend/auth/migrate” "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko).
i use docker-compose:

version: "3.7"
services:
  app:
    image: myoctober-v2:latest
    container_name: kwest-v2-app
    working_dir: /var/www/html
    volumes:
      - ../../:/var/www/html:rw 
    environment:
      COMPOSER_MEMORY_LIMIT: -1
      DB_CONNECTION: ${DB_CONNECTION}
      DB_HOST: ${DB_HOST}
      DB_PORT: ${DB_PORT}
      DB_DATABASE: ${DB_DATABASE}
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      ENABLE_CRON: ${ENABLE_CRON}
      PHP_DISPLAY_ERRORS: ${PHP_DISPLAY_ERRORS}
      APP_DEBUG: ${APP_DEBUG}
      CMS_LINK_POLICY: secure
    networks:
      default:
        ipv4_address: 172.18.1.2

  db:
    image: mysql:5.7
    container_name: kwest-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: kwest
      MYSQL_ROOT_PASSWORD: 123qwer
      MYSQL_PASSWORD: kwest
      MYSQL_USER: kwest
    volumes:
      - ./mysql_data:/var/lib/mysql/
    networks:
      default:
        ipv4_address: 172.18.1.3

  haproxy:
    image: "haproxy:2.2-alpine"
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
      - ./ssl/example.com.pem:/etc/ssl/certs/example.com.pem
    networks:
      default:
        ipv4_address: 172.18.1.10


networks:
  default:
    external:
       name: develop

Config haproxy:

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
frontend http_frontend
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/example.com.pem
    redirect scheme https code 301 if !{ ssl_fc }
    acl is_app hdr_end(host) -i example.com
    use_backend app if is_app
    acl letsencrypt-acl path_beg /.well-known/acme-challenge/
    use_backend letsencrypt-backend if letsencrypt-acl
backend letsencrypt-backend
    server letsencrypt 127.0.0.1:8888
backend app
    server app 172.18.1.2:80 check

Сan’t find the reason, what am I doing wrong?

Dockerfile

FROM php:7.4-apache

RUN apt-get update && apt-get install -y cron git-core jq unzip vim zip \
  libjpeg-dev libpng-dev libpq-dev libsqlite3-dev libwebp-dev libzip-dev && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-configure zip --with-zip && \
  docker-php-ext-configure gd --with-jpeg --with-webp && \
  docker-php-ext-install exif gd mysqli opcache pdo_pgsql pdo_mysql zip

RUN { \
    echo 'opcache.memory_consumption=128'; \
    echo 'opcache.interned_strings_buffer=8'; \
    echo 'opcache.max_accelerated_files=4000'; \
    echo 'opcache.revalidate_freq=2'; \
    echo 'opcache.fast_shutdown=1'; \
    echo 'opcache.enable_cli=1'; \
  } > /usr/local/etc/php/conf.d/docker-oc-opcache.ini

RUN { \
    echo 'log_errors=on'; \
    echo 'display_errors=off'; \
    echo 'upload_max_filesize=32M'; \
    echo 'post_max_size=32M'; \
    echo 'memory_limit=128M'; \
  } > /usr/local/etc/php/conf.d/docker-oc-php.ini

ENV COMPOSER_ALLOW_SUPERUSER=1

RUN curl -sS https://getcomposer.org/installer | php -- --1 --install-dir=/usr/local/bin --filename=composer && \
  /usr/local/bin/composer global require hirak/prestissimo

RUN a2enmod rewrite

COPY config/docker /usr/src/octobercms-config-docker

COPY ../../* /var/www/html

RUN echo 'APP_ENV=docker' > .env && \
  chown -R www-data:www-data /var/www/html && \
  find . -type d \( -path './plugins' -or  -path './storage' -or  -path './themes' -or  -path './plugins/*' -or  -path './storage/*' -or  -path './themes/*' \) -exec chmod g+ws {} \;

RUN echo "* * * * * /usr/local/bin/php /var/www/html/artisan schedule:run > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/cron.d/october-cron && \
  crontab /etc/cron.d/october-cron

RUN echo 'exec php artisan "$@"' > /usr/local/bin/artisan && \
  echo 'exec php artisan tinker' > /usr/local/bin/tinker && \
  echo '[ $# -eq 0 ] && exec php artisan october || exec php artisan october:"$@"' > /usr/local/bin/october && \
  sed -i '1s;^;#!/bin/bash\n[ "$PWD" != "/var/www/html" ] \&\& echo " - Helper must be run from /var/www/html" \&\& exit 1\n;' /usr/local/bin/artisan /usr/local/bin/tinker /usr/local/bin/october && \
  chmod +x /usr/local/bin/artisan /usr/local/bin/tinker /usr/local/bin/october

COPY docker-oc-entrypoint /usr/local/bin/

ENTRYPOINT ["docker-oc-entrypoint"]
CMD ["apache2-foreground"]

When using a reverse proxy, make sure you have no rules redirecting HTTP to HTTPS on the local server (backend.force_secure). Also check that sessions are available across HTTP and HTTPS (session.http_only).

Thanks for the reply. I change the parameters, but it doesn’t help.
config/session.php
‘http_only’ => false,

config/backend.php
‘force_secure’ => false,

there is an error in the browser: Mixed Content: The page at 'https://example.com/backend/backend/auth/migrate' was loaded over HTTPS, but requested an insecure stylesheet 'http://example.com/modules/system/assets/ui/storm.css?v=6adddf60'. This request has been blocked; the content must be served over HTTPS

in .env, such parameters are

PROJECT_NAME=kwest-back
ACTIVE_THEME=map
APP_DEBUG=true
APP_ENV=local
APP_KEY=******************************************
APP_LOCALE=en
APP_NAME=October_CMS
APP_URL=https://example.com
BACKEND_URI=/backend
BROADCAST_DRIVER=log
CACHE_DRIVER=file
CMS_ASSET_CACHE=false
CMS_DB_TEMPLATES=false
CMS_ROUTE_CACHE=false
COMPOSE_PROJECT_NAME=kwest-back
DB_CONNECTION=mysql
DB_DATABASE=kwest
DB_HOST=mysql-server
DB_PASSWORD=kwest
DB_PORT=3306
DB_USERNAME=kwest
DEFAULT_FILE_MASK=775
DEFAULT_FOLDER_MASK=775
LINK_POLICY=detect
LOG_CHANNEL=single

APP_URL=https:// and APP_URL=http:// - nothing changes
LINK_POLICY=secure or force - js and css start loading, but the POST request 403

It worked in this configuration: haproxy+nginx+php-fpm
80 → haproxy → tcp port 443 → nginx ssl → php port-fpm 9000
I think the application needs the client’s real ip addresses. And reverse proxy did not pass such parameters. Docker’s internal ip addresses came out in the logs.
Am I thinking right or am I wrong?
Parameters with which it worked:
haproxy.cfg

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    log global
frontend http_frontend
    bind *:80
    redirect scheme https code 301 if !{ ssl_fc }
frontend https_frontend
    bind *:443
    option tcplog
    mode tcp
    default_backend nginx
    stats uri /haproxy?stats
backend nginx
    mode tcp
    option ssl-hello-chk
    balance roundrobin
    server nginx 172.18.1.1:443 send-proxy

app.conf

server {
    listen *:443 default_server ssl proxy_protocol; 
    server_name example.com;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html;

#   server_tokens off;

        ssl_certificate /lets/fullchain.pem;
        ssl_certificate_key /lets/privkey.pem;
        set_real_ip_from 172.18.1.0/24;
        real_ip_header proxy_protocol;
 

client_max_body_size 8M; 

    location / {
        proxy_max_temp_file_size 0;
        try_files $uri $uri/ /index.php?$query_string;
    }


location ~ ^/index.php {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    try_files $uri =404;
    fastcgi_pass app:9000;
    fastcgi_read_timeout 300;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
}

location ~ ^/(.*\.(ac3|avi|bmp|bz2|css|cue|dat|doc|docx|dts|exe|flv|gif|gz|html|ico|img|iso|jpeg|jpg|js|mkv|mp3|mp4|mpeg|mpg|ogg|pdf|png|ppt|pptx|qt|rar|rm|swf|tar|tgz|txt|wav|xls|xlsx|zip|7z|svg|ttf|woff|woff2|eot))$ {
    sendfile on;
    access_log off;
    expires max;
}

location ~ /\.ht {
    deny all;
}

# Whitelist

## Let October handle if static file not exists
location ~ ^/favicon\.ico { try_files $uri /index.php; }
location ~ ^/sitemap\.xml { try_files $uri /index.php; }
location ~ ^/robots\.txt { try_files $uri /index.php; }
location ~ ^/humans\.txt { try_files $uri /index.php; }
location ~ ^/[0-9a-z]+.html { try_files $uri /index.php; }
location ~ ^/[0-9a-z]+.tar.gz {try_files $uri /index.php; }

## Let nginx return 404 if static file not exists
location ~ ^/.well-known { try_files $uri 404; }
location ~ ^/storage/app/uploads/public { try_files $uri 404; }
location ~ ^/storage/app/media { try_files $uri 404; }
location ~ ^/storage/temp/public { try_files $uri 404; }
location ~ ^/storage/app/cropped { try_files $uri 404; }
location ~ ^/storage/app/rss { try_files $uri 404; }
location ~ ^/storage/app/collections { try_files $uri 404; }
location ~ ^/modules/.*/assets { try_files $uri 404; }
location ~ ^/modules/.*/resources { try_files $uri 404; }
location ~ ^/modules/.*/behaviors/.*/assets { try_files $uri 404; }
location ~ ^/modules/.*/behaviors/.*/resources { try_files $uri 404; }
location ~ ^/modules/.*/widgets/.*/assets { try_files $uri 404; }
location ~ ^/modules/.*/widgets/.*/resources { try_files $uri 404; }
location ~ ^/modules/.*/formwidgets/.*/assets { try_files $uri 404; }
location ~ ^/modules/.*/formwidgets/.*/resources { try_files $uri 404; }
location ~ ^/modules/.*/reportwidgets/.*/assets { try_files $uri 404; }
location ~ ^/modules/.*/reportwidgets/.*/resources { try_files $uri 404; }
location ~ ^/plugins/.*/.*/assets { try_files $uri 404; }
location ~ ^/plugins/.*/.*/resources { try_files $uri 404; }
location ~ ^/plugins/.*/.*/behaviors/.*/assets { try_files $uri 404; }
location ~ ^/plugins/.*/.*/behaviors/.*/resources { try_files $uri 404; }
location ~ ^/plugins/.*/.*/reportwidgets/.*/assets { try_files $uri 404; }
location ~ ^/plugins/.*/.*/reportwidgets/.*/resources { try_files $uri 404; }
location ~ ^/plugins/.*/.*/formwidgets/.*/assets { try_files $uri 404; }
location ~ ^/plugins/.*/.*/formwidgets/.*/resources { try_files $uri 404; }
location ~ ^/plugins/.*/.*/widgets/.*/assets { try_files $uri 404; }
location ~ ^/plugins/.*/.*/widgets/.*/resources { try_files $uri 404; }
location ~ ^/themes/.*/assets { try_files $uri 404; }
location ~ ^/themes/.*/resources { try_files $uri 404; }


gzip on;
gzip_disable "msie6";
gzip_types
application/atom+xml
application/javascript
text/javascript
application/json
application/ld+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
font/ttf
application/x-font-ttf
application/vnd.ms-fontobject
application/font-woff
application/font-woff2
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vnd.wap.wml
text/vtt
text/x-component
text/x-cross-domain-policy;
gzip_comp_level 6;
gzip_vary on;
gzip_static off;
gzip_proxied any;



}


server {


    listen 80;
    server_name example.com;
    location / {
                rewrite ^ https://$host$request_uri? permanent;
        }

}

You may need to set the URL (app.url) to something static and then force the schema with a link policy (system.link_policy) set to force.