Superuser login fails with "Form security" error on Matomo 4.8.0 using nginx - dockerized nginx / php

trying to run matomo on docker, using nginx / fpm, behind a gateway nginx that’s also serving other vhosts on that machine. Think I read all the bug reports and threads of that error that happened during the last 3 years, found no solution. no console logs.

Error

mat_err

When logging in: https://<my.domain.io>/?module=Login
Symptom: Err message on login form

Error : Form security failed, invalid origin. If you previously connected using https, please ensure you are connecting over a secure (SSL/TLS) connection and try again.

Browser console

POST https://my.domain.io/?module=Login → 403 Forbidden

Another curious symptom, browser console says:

Content Security Policy: The page’s settings blocked the loading of a resource at https:///plugins/Morpheus/images/logo.svg?matomo (“img-src”).

My setup:

client request → nginx proxy as application proxy (also runs other vhosts)
→ docker nginx
→ docker fpm / matomo

outer nginx delivers ssl termination, setup and login page correctly work with SSL.

my system’s nginx vhost

server {
    server_name my.domain.io;
#    add_header Referrer-Policy origin always; # make sure outgoing links don't show th
e URL to the Matomo instance
    location / {
	    proxy_pass http://127.0.0.1:5959; 
            proxy_set_header Host $host;
            proxy_set_header  X-Real-IP $remote_addr;
            proxy_set_header  X-Forwarded-Proto https;
            proxy_set_header  X-Forwarded-For $remote_addr;
            proxy_set_header  X-Forwarded-Host $remote_addr;
            proxy_set_header Cookie $http_cookie;
            #proxy_cache_bypass $http_upgrade;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/my.domain.io/fullchain.pem; # managed by Ce
rtbot
    ssl_certificate_key /etc/letsencrypt/live/my.domain.io/privkey.pem; # managed by 
Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}


server {
    if ($host = my.domain.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name my.domain.io;
    listen 80;
    return 404; # managed by Certbot
}

docker-compose

version: "3"

services:
  matomo_db:
...db...

  app:
    image: matomo:fpm-alpine
    restart: always
    links:
      - matomo_db
    volumes:
      - ./config:/var/www/html/config:rw
      - ./logs:/var/www/html/logs
      - matomo:/var/www/html
    environment:
      - MATOMO_DATABASE_HOST=matomo_db
      - PHP_MEMORY_LIMIT=2048M
    env_file:
      - ./db.env

  web:
    image: nginx:alpine
    restart: always
    volumes:
      - matomo:/var/www/html:ro
      # see https://github.com/matomo-org/matomo-nginx
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app
    ports:
      - 5959:80

volumes:
  matomo:

docker nginx / fpm config (from git), added more headers out of desperation.

upstream php-handler {
	server app:9000;
}

server {
	listen 80;

  # add_header X-Content-Type-Options "nosniff" always;
  # add_header X-XSS-Protection "1; mode=block" always;
  # add_header Referrer-Policy origin always; # make sure outgoing links don't show the URL to the Matomo instance

	root /var/www/html; # replace with path to your matomo instance
	index index.php;
	try_files $uri $uri/ =404;

	## only allow accessing the following php files
	location ~ ^/(index|matomo|piwik|js/index|plugins/HeatmapSessionRecording/conf
igs).php {
		# regex to split $uri to $fastcgi_script_name and $fastcgi_path
		fastcgi_split_path_info ^(.+\.php)(/.+)$;

		# Check that the PHP script exists before passing it
		try_files $fastcgi_script_name =404;

		include fastcgi_params;
                fastcgi_param  SERVER_NAME        $host;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
		fastcgi_param HTTP_PROXY ""; # prohibit httpoxy: https://httpoxy.org/
		fastcgi_pass php-handler;
	}

	## deny access to all other .php files
	location ~* ^.+\.php$ {
		deny all;
		return 403;
	}

... see your docker matomo git repo

config.ini.php - added more stuff out of desperation

db...

[General]
proxy_client_headers[] = "HTTP_X_FORWARDED_FOR"
proxy_client_headers[] = "HTTP_X_FORWARDED_FOR"
proxy_host_headers[] = "HTTP_X_FORWARDED_HOST"
proxy_host_headers[] = "HTTP_X_FORWARDED_HOST"
salt = "9c6d7a....9"
trusted_hosts[] = "my.domain.io"
trusted_hosts[] = "domain.io"
//also tried docker host names & ips

enable_trusted_host_check=0

force_ssl = 1
assume_secure_protocol = 1

login request logs:

app_1        | 172.30.0.4 -  12/Mar/2022:21:16:28 +0000 "GET /index.php" 200
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:28 +0000] "GET /?module=Login HTTP/1.0" 200 74892 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
app_1        | 172.30.0.4 -  12/Mar/2022:21:16:28 +0000 "GET /index.php" 200
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:28 +0000] "GET /index.php?module=Proxy&action=getCss&cb=d74435c4e8d105a12bc4dc5f58b93ed4 HTTP/1.0" 200 76980 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
app_1        | 172.30.0.4 -  12/Mar/2022:21:16:28 +0000 "GET /index.php" 200
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:28 +0000] "GET /index.php?module=Proxy&action=getCoreJs&cb=870ad0fa8d2eb2e3fefe811ff62720d3 HTTP/1.0" 200 660066 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
app_1        | 172.30.0.4 -  12/Mar/2022:21:16:28 +0000 "GET /index.php" 200
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:28 +0000] "GET /index.php?module=Proxy&action=getNonCoreJs&cb=870ad0fa8d2eb2e3fefe811ff62720d3 HTTP/1.0" 200 81 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:28 +0000] "GET /plugins/Morpheus/images/loading-blue.gif HTTP/1.0" 200 723 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:29 +0000] "GET /plugins/Morpheus/fonts/matomo.woff2?rjeutj HTTP/1.0" 200 11528 "https://my.domain.io/index.php?module=Proxy&action=getCss&cb=d74435c4e8d105a12bc4dc5f58b93ed4" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
app_1        | 172.30.0.4 -  12/Mar/2022:21:16:29 +0000 "POST /index.php" 200
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:29 +0000] "POST /?module=API&format=json&method=API.getPagesComparisonsDisabledFor&segment=&date= HTTP/1.0" 200 230 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:29 +0000] "GET /plugins/CoreHome/images/applogo_256.png HTTP/1.0" 200 10278 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"


app_1        | 172.30.0.4 -  12/Mar/2022:21:16:35 +0000 "POST /index.php" 403
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:35 +0000] "POST /?module=Login HTTP/1.0" 403 75300 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"
app_1        | 172.30.0.4 -  12/Mar/2022:21:16:36 +0000 "POST /index.php" 200
web_1        | 172.30.0.1 - - [12/Mar/2022:21:16:36 +0000] "POST /?module=API&format=json&method=API.getPagesComparisonsDisabledFor&segment=&date= HTTP/1.0" 200 230 "https://my.domain.io/?module=Login" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "95.91.247.70"

Hi,

I am not an expert on servers… but I’ll try to help you to find some clues…

Strange, it looks like sour server domain is not well set.
Indeed, looking at the demo Matomo site login page, the logo URI is: https://demo.matomo.cloud/plugins/Morpheus/images/logo.svg?matomo

That error message is related to CSRF security tokens, I assume (msg is called InvalidNonceOrigin). Wondering if the trailing slash of referrer and origin might be an issue:

The error is thrown here: https://github.com/matomo-org/matomo/blob/b183ce903f3e24c97ad929c77aff99e1ef93ab11/core/Nonce.php#L136

oh, interesting. I just disabled the Nonce/Origin check and that has successfully authenticated me. The redirect led me to my own IP though… which seems to hint to the root cause you mentioned, @heurteph-ei . Where to set the server domain?! That’s a PHP var, I’d assume?

I have no knowledge at all on Docker.
The only thing I read from https://www.grottedubarbu.fr/matomo-docker/ is that the Matomo URL is set at the beginning of installation:

MATOMO_URL=matomo.mydomain.com \
docker-compose up -d

The issue was the forwarding of ips and host names from the user side. That’s not really related to Docker in general, but was caused by the way nginx proxies forward server / host names. The fix basically was to actviely forward $server_name to the docker nginx:

proxy_set_header Host $server_name;

I updated (reduced) my configs, happy to share.

nginx at gateway side

server {
    server_name t.example.io;
    location / {
	    proxy_pass http://127.0.0.1:5959; 

# ---- THIS IS IMPORTANT:
            proxy_set_header Host $server_name;
# ---- ----
            proxy_set_header  X-Real-IP $remote_addr;
            proxy_set_header  X-Forwarded-Proto https;
            proxy_set_header  X-Forwarded-For $remote_addr;
            proxy_set_header Cookie $http_cookie;
            #proxy_cache_bypass $http_upgrade;
    }

    listen 443 ssl; # managed by Certbot
#    ... ssl

}

nginx container

(see original docker nginx setup by Matomo)

upstream php-handler {
	server app:9000;
}

server {
    listen 80;
    add_header Referrer-Policy origin always; # make sure outgoing links don't show the URL to the Matomo instance

	root /var/www/html; # replace with path to your matomo instance
	index index.php;
	try_files $uri $uri/ =404;

	## only allow accessing the following php files
	location ~ ^/(index|matomo|piwik|js/index|plugins/HeatmapSessionRecording/configs).php {
		# regex to split $uri to $fastcgi_script_name and $fastcgi_path
		fastcgi_split_path_info ^(.+\.php)(/.+)$;

		# Check that the PHP script exists before passing it
		try_files $fastcgi_script_name =404;

		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_param PATH_INFO $fastcgi_path_info;
		fastcgi_param HTTP_PROXY ""; # prohibit httpoxy: https://httpoxy.org/
		fastcgi_pass php-handler;
	}

config.ini.php

[General]
proxy_client_headers[] = "HTTP_X_FORWARDED_FOR"
proxy_host_headers[] = "HTTP_X_FORWARDED_HOST"
salt = "..."
trusted_hosts[] = "t.example.io"
trusted_hosts[] = "example.io"