Config.php(68): Warning - array_key_exists() expects parameter 2 to be array, string given

Hi,

Currently seing random issues within the admin UI of Matomo that I am unable to debug myself.

Matomo is run in a multi container environment.

Example of error url:
index.php?module=PrivacyManager&action=privacySettings&date=today&period=week&idSite=8

Randomly, this spits out these warnings:

WARNING: /var/www/plugins/PrivacyManager/Config.php(68): Warning - array_key_exists() expects parameter 2 to be array, string given - Matomo 3.8.1 - Please report this message in the Matomo forums: https://forum.matomo.org (please do a search first as it might have been reported already)
×
WARNING: /var/www/plugins/CustomVariables/CustomVariables.php(102): Warning - Illegal string offset 'CustomVariables.MaxNumCustomVariables' - Matomo 3.8.1 - Please report this message in the Matomo forums: https://forum.matomo.org (please do a search first as it might have been reported already)
×
WARNING: /var/www/plugins/CustomVariables/CustomVariables.php(81): Warning - array_key_exists() expects parameter 2 to be array, string given - Matomo 3.8.1 - Please report this message in the Matomo forums: https://forum.matomo.org (please do a search first as it might have been reported already)

I also notice that if I change the settings in the PrivacyManger page, it is not stored. Hard refreshing the page a few times and the old settings is present.

I’ve provided the nginx.conf, config.ini.php

config.ini.php:

; <?php exit; ?> DO NOT REMOVE THIS LINE
; file automatically generated or modified by Matomo; you can manually override the default values in global.ini.php by redefining them in this file.
[database]
host = "%%DBHOST%%"
username = "%%MYSQL_USER%%"
password = "%%MYSQL_PASSWORD%%"
dbname = "%%MYSQL_DBNAME%%"
tables_prefix = "matomo_"


[General]
salt = "%%MATOMO_SALT%%"
trusted_hosts[] = "%%MATOMO_HOSTNAME%%"
trusted_hosts[] = "%%MATOMO_TRACKER_HOSTNAME%%"

; disable browser trigger archiving for all requests (even those with a segment)
browser_archiving_disabled_enforce = 0

multi_server_environment = 1

; when set to 1, all requests to Matomo will return a maintenance message without connecting to the DB
; this is useful when upgrading using the shell command, to prevent other users from accessing the UI while Upgrade is in progress
maintenance_mode = %%MATOMO_MAINTENANCE_MODE%%

; by default, the real time Live! widget will update every 5 seconds and refresh with new visits/actions/etc.
; you can change the timeout so the widget refreshes more often, or not as frequently
live_widget_refresh_after_seconds = 5

; by default, the Live! real time visitor count widget will check to see how many visitors your
; website received in the last 3 minutes. changing this value will change the number of minutes
; the widget looks in.
live_widget_visitor_count_last_minutes = 5

; In "All Websites" dashboard, when looking at today's reports (or a date range including today),
; the page will automatically refresh every 5 minutes. Set to 0 to disable automatic refresh
multisites_refresh_after_seconds = 0

; If set to 1, Matomo will automatically redirect all http:// requests to https://
; If SSL / https is not correctly configured on the server, this will break Matomo
; If you set this to 1, and your SSL configuration breaks later on, you can always edit this back to 0
; it is recommended for security reasons to always use Matomo over https
force_ssl = 1
assume_secure_protocol = 1

; by default, an update notification for a new version of Matomo is shown to every user. Set to 1 if only
; the superusers should see the notification.
show_update_notification_to_superusers_only = 1

; Uncomment line below if you use a standard proxy
proxy_client_headers[] = HTTP_X_FORWARDED_FOR
proxy_host_headers[] = HTTP_X_FORWARDED_HOST


; In some rare cases it may be useful to explicitely tell Matomo not to use LOAD DATA INFILE
; This may for example be useful when doing Mysql AWS replication
enable_load_data_infile = 1

; by default, Matomo uses PHP's built-in file-based session save handler with lock files.
; For clusters, use dbtable.
; We did use redis, but not supported since 3.8.0
session_save_handler = dbtable

; If set to 0 it will disable advertisements for providers of Professional Support for Matomo.
piwik_professional_support_ads_enabled = 0

; By setting this option to 0, it will disable the "Auto update" feature
enable_auto_update = 0

; By setting this option to 0 (e.g. in common.config.ini.php) the installer will be disabled.
enable_installer = %%MATOMO_ENABLE_INSTALLER%%

; By setting this option to 0:
; - links to Enable/Disable/Uninstall plugins will be hidden and disabled
; - links to Uninstall themes will be disabled (but user can still enable/disable themes)
enable_plugins_admin = 0

; By setting this option to 1, it will be possible for Super Users to upload Matomo plugin ZIP archives directly in Matomo Administration.
; Enabling this opens a remote code execution vulnerability where
; an attacker who gained Super User access could execute custom PHP code in a Matomo plugin.
enable_plugin_upload = 0

; By default we check whether the Custom logo is writable or not, before we display the Custom logo file uploader
enable_custom_logo_check = 0

; If you use this Matomo instance over multiple hostnames, Matomo will need to know
; a unique instance_id for this instance, so that Matomo can serve the right custom logo and tmp/* assets,
; independently of the hostname Matomo is currently running under.
instance_id = %%MATOMO_HOSTNAME%%

[log]
; possible values for log: screen, database, file
log_writers[] = screen

; log level, everything logged w/ this level or one of greater severity
; will be logged. everything else will be ignored. possible values are:
; ERROR, WARN, INFO, DEBUG
log_level = %%MATOMO_LOG_LEVEL%%


[Plugins]
Plugins[] = "CorePluginsAdmin"
Plugins[] = "CoreAdminHome"
Plugins[] = "CoreHome"
Plugins[] = "WebsiteMeasurable"
Plugins[] = "IntranetMeasurable"
Plugins[] = "Diagnostics"
Plugins[] = "CoreVisualizations"
Plugins[] = "Proxy"
Plugins[] = "API"
Plugins[] = "ExamplePlugin"
Plugins[] = "Widgetize"
Plugins[] = "Transitions"
Plugins[] = "LanguagesManager"
Plugins[] = "Actions"
Plugins[] = "Dashboard"
Plugins[] = "MultiSites"
Plugins[] = "Referrers"
Plugins[] = "UserLanguage"
Plugins[] = "DevicesDetection"
Plugins[] = "Goals"
Plugins[] = "Ecommerce"
Plugins[] = "SEO"
Plugins[] = "Events"
Plugins[] = "UserCountry"
Plugins[] = "GeoIp2"
Plugins[] = "VisitsSummary"
Plugins[] = "VisitFrequency"
Plugins[] = "VisitTime"
Plugins[] = "VisitorInterest"
Plugins[] = "ExampleAPI"
Plugins[] = "RssWidget"
Plugins[] = "Feedback"
Plugins[] = "Monolog"
Plugins[] = "Login"
Plugins[] = TwoFactorAuth
Plugins[] = "UsersManager"
Plugins[] = "SitesManager"
Plugins[] = "Installation"
Plugins[] = "CoreUpdater"
Plugins[] = "CoreConsole"
Plugins[] = "ScheduledReports"
Plugins[] = "UserCountryMap"
Plugins[] = "Live"
Plugins[] = "CustomVariables"
Plugins[] = "PrivacyManager"
Plugins[] = "ImageGraph"
Plugins[] = "Annotations"
Plugins[] = "MobileMessaging"
Plugins[] = "Overlay"
Plugins[] = "SegmentEditor"
Plugins[] = "Insights"
Plugins[] = "Morpheus"
Plugins[] = "Contents"
Plugins[] = "BulkTracking"
Plugins[] = "Resolution"
Plugins[] = "DevicePlugins"
Plugins[] = "Heartbeat"
Plugins[] = "Intl"
Plugins[] = "Marketplace"
Plugins[] = "ProfessionalServices"
Plugins[] = "UserId"
Plugins[] = "CustomPiwikJs"
Plugins[] = "QueuedTracking"
Plugins[] = "DBStats"
Plugins[] = "TasksTimetable"
Plugins[] = "CustomDimensions"
Plugins[] = "CustomReports"
Plugins[] = "InvalidateReports"

[PluginsInstalled]
PluginsInstalled[] = "Diagnostics"
PluginsInstalled[] = "Login"
PluginsInstalled[] = TwoFactorAuth
PluginsInstalled[] = "CoreAdminHome"
PluginsInstalled[] = "UsersManager"
PluginsInstalled[] = "SitesManager"
PluginsInstalled[] = "Installation"
PluginsInstalled[] = "Monolog"
PluginsInstalled[] = "Intl"
PluginsInstalled[] = "CorePluginsAdmin"
PluginsInstalled[] = "CoreHome"
PluginsInstalled[] = "WebsiteMeasurable"
PluginsInstalled[] = "IntranetMeasurable"
PluginsInstalled[] = "CoreVisualizations"
PluginsInstalled[] = "Proxy"
PluginsInstalled[] = "API"
PluginsInstalled[] = "ExamplePlugin"
PluginsInstalled[] = "Widgetize"
PluginsInstalled[] = "Transitions"
PluginsInstalled[] = "LanguagesManager"
PluginsInstalled[] = "Actions"
PluginsInstalled[] = "Dashboard"
PluginsInstalled[] = "MultiSites"
PluginsInstalled[] = "Referrers"
PluginsInstalled[] = "UserLanguage"
PluginsInstalled[] = "DevicesDetection"
PluginsInstalled[] = "Goals"
PluginsInstalled[] = "Ecommerce"
PluginsInstalled[] = "SEO"
PluginsInstalled[] = "Events"
PluginsInstalled[] = "UserCountry"
PluginsInstalled[] = "GeoIp2"
PluginsInstalled[] = "VisitsSummary"
PluginsInstalled[] = "VisitFrequency"
PluginsInstalled[] = "VisitTime"
PluginsInstalled[] = "VisitorInterest"
PluginsInstalled[] = "ExampleAPI"
PluginsInstalled[] = "RssWidget"
PluginsInstalled[] = "Feedback"
PluginsInstalled[] = "CoreUpdater"
PluginsInstalled[] = "CoreConsole"
PluginsInstalled[] = "ScheduledReports"
PluginsInstalled[] = "UserCountryMap"
PluginsInstalled[] = "Live"
PluginsInstalled[] = "CustomVariables"
PluginsInstalled[] = "PrivacyManager"
PluginsInstalled[] = "ImageGraph"
PluginsInstalled[] = "Annotations"
PluginsInstalled[] = "MobileMessaging"
PluginsInstalled[] = "Overlay"
PluginsInstalled[] = "SegmentEditor"
PluginsInstalled[] = "Insights"
PluginsInstalled[] = "Morpheus"
PluginsInstalled[] = "Contents"
PluginsInstalled[] = "BulkTracking"
PluginsInstalled[] = "Resolution"
PluginsInstalled[] = "DevicePlugins"
PluginsInstalled[] = "Heartbeat"
PluginsInstalled[] = "Marketplace"
PluginsInstalled[] = "ProfessionalServices"
PluginsInstalled[] = "UserId"
PluginsInstalled[] = "CustomPiwikJs"
PluginsInstalled[] = "QueuedTracking"
PluginsInstalled[] = "TagManager"
PluginsInstalled[] = "DBStats"
PluginsInstalled[] = "TasksTimetable"
PluginsInstalled[] = "CustomDimensions"
PluginsInstalled[] = "CustomReports"
PluginsInstalled[] = "InvalidateReports"

[QueuedTracking]
notify_queue_threshold_single_queue = 250000
; Below config is not really needed sincce the processQueue.sh script queries Redis manually before starting an process-job
backend = "redis"
redisHost = "%%REDIS_HOST%%"
redisPort = 6379
redisDatabase = 2
redisPassword = ""
queueEnabled = true
numQueueWorkers = %%QUEUEDTRACKING_WORKERS%%
processDuringTrackingRequest = false
numRequestsToProcess = %%QUEUEDTRACKING_NUM_REQUEST_TO_PROCESS%%
useSentinelBackend = false
sentinelMasterName = "HardCodedIn config.ini.php"

[Cache]
; available backends are 'file', 'array', 'null', 'redis', 'chained'
; 'array' will cache data only during one request
; 'null' will not cache anything at all
; 'file' will cache on the filesystem
; 'redis' will cache on a Redis server, use this if you are running Matomo with multiple servers. Further configuration in [RedisCache] is needed
; 'chained' will chain multiple cache backends. Further configuration in [ChainedCache] is needed
backend = redis

[ChainedCache]
backends[] = array
backends[] = redis

[RedisCache]
; Redis server configuration.
host = "%%REDIS_HOST%%"
port = 6379
; instead of host and port a unix socket path can be configured
;unix_socket = ""
timeout = 0.0
password = ""
database = 0
; In case you are using queued tracking: Make sure to configure a different database! Otherwise queued requests might
; be flushed

[Debug]
enable_sql_profiler = %%MATOMO_SQL_PROFILING%%

nginx.conf:

daemon off;
user nginx;
pid /run/nginx.pid;
worker_processes auto;
error_log /proc/1/fd/2 info;
worker_rlimit_nofile 8192;

load_module /etc/nginx/modules/ngx_http_geoip2_module.so;

events {
    worker_connections 2048;
    use epoll;
    multi_accept on;
}

http {
    include mime.types;
    default_type application/octet-stream;
    access_log /proc/1/fd/1;

    aio threads;
    sendfile on;

    # Variables hash table size
    variables_hash_bucket_size 64;
    variables_hash_max_size 2048;

    # GeoIP 2
    geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
        auto_reload 5m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_geonameid country geoname_id;
        $geoip2_data_country_iso country iso_code;
        $geoip2_data_country_name country names en;
        $geoip2_data_country_is_eu country is_in_european_union;
    }
    geoip2 /etc/nginx/geoip/GeoLite2-City.mmdb {
        auto_reload 5m;
        $geoip2_data_city_name city names en;
        $geoip2_data_city_geonameid city geoname_id;
        $geoip2_data_continent_code continent code;
        $geoip2_data_continent_geonameid continent geoname_id;
        $geoip2_data_continent_name continent names en;
        $geoip2_data_location_accuracyradius location accuracy_radius;
        $geoip2_data_location_latitude location latitude;
        $geoip2_data_location_longitude location longitude;
        $geoip2_data_location_metrocode location metro_code;
        $geoip2_data_location_timezone location time_zone;
        $geoip2_data_postal_code postal code;
        $geoip2_data_rcountry_geonameid registered_country geoname_id;
        $geoip2_data_rcountry_iso registered_country iso_code;
        $geoip2_data_rcountry_name registered_country names en;
        $geoip2_data_rcountry_is_eu registered_country is_in_european_union;
        $geoip2_data_region_geonameid subdivisions 0 geoname_id;
        $geoip2_data_region_iso subdivisions 0 iso_code;
        $geoip2_data_region_name subdivisions 0 names en;
    }
    geoip2 /etc/nginx/geoip/GeoLite2-ASN.mmdb {
        auto_reload 5m;
        $geoip2_data_org autonomousSystemOrganization;
    }

    ## Timeouts
    client_body_timeout   60;
    client_header_timeout 60;
    keepalive_timeout     10 10;
    send_timeout          60;

    ## TCP options
    tcp_nopush  on;
    tcp_nodelay on;

    ## Handling of IPs in proxied and load balancing situations
    real_ip_header X-Forwarded-For;
    set_real_ip_from 10.0.0.0/8;

    ## Hide the Nginx version number
    server_tokens off;

    ## Body size
    client_max_body_size 64M;
    client_body_buffer_size 128k;

    ## Compression
    gzip              on;
    gzip_buffers      16 8k;
    gzip_comp_level   1;
    gzip_http_version 1.1;
    gzip_min_length   10;
    gzip_types        text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon application/vnd.ms-fontobject font/opentype application/x-font-ttf;
    gzip_vary         on;
    gzip_proxied      any;
    gzip_disable      "msie6";

    ## Serve already compressed files directly, bypassing on-the-fly compression
    gzip_static on;

    ## FastCGI
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    fastcgi_buffers 256 4k;
    fastcgi_intercept_errors on;
    fastcgi_read_timeout 14400;

    ## FastCGI GeoIP 2
    fastcgi_param MM_ADDR $remote_addr;
    fastcgi_param MMDB_ADDR $remote_addr;
    fastcgi_param MM_CITY_NAME $geoip2_data_city_name;
    fastcgi_param MM_CITY_GEONAMEID $geoip2_data_city_geonameid;
    fastcgi_param MM_CONTINENT_CODE $geoip2_data_continent_code;
    fastcgi_param MM_CONTINENT_GEONAMEID $geoip2_data_continent_geonameid;
    fastcgi_param MM_CONTINENT_NAME $geoip2_data_continent_name;
    fastcgi_param MM_COUNTRY_GEONAMEID $geoip2_data_country_geonameid;
    fastcgi_param MM_COUNTRY_CODE $geoip2_data_country_iso;
    fastcgi_param MM_COUNTRY_NAME $geoip2_data_country_name;
    fastcgi_param MM_COUNTRY_IN_EU $geoip2_data_country_is_eu;
    fastcgi_param MM_LOCATION_ACCURACY_RADIUS $geoip2_data_location_accuracyradius;
    fastcgi_param MM_LATITUDE $geoip2_data_location_latitude;
    fastcgi_param MM_LONGITUDE $geoip2_data_location_longitude;
    fastcgi_param MM_LOCATION_METROCODE $geoip2_data_location_metrocode;
    fastcgi_param MM_LOCATION_TIMEZONE $geoip2_data_location_timezone;
    fastcgi_param MM_POSTAL_CODE $geoip2_data_postal_code;
    fastcgi_param MM_REGISTERED_COUNTRY_GEONAMEID $geoip2_data_rcountry_geonameid;
    fastcgi_param MM_REGISTERED_COUNTRY_ISO $geoip2_data_rcountry_iso;
    fastcgi_param MM_REGISTERED_COUNTRY_NAME $geoip2_data_rcountry_name;
    fastcgi_param MM_REGISTERED_COUNTRY_IN_EU $geoip2_data_rcountry_is_eu;
    fastcgi_param MM_REGION_GEONAMEID $geoip2_data_region_geonameid;
    fastcgi_param MM_REGION $geoip2_data_region_iso;
    fastcgi_param MM_REGION_NAME $geoip2_data_region_name;
    fastcgi_param MM_ISP $geoip2_data_org;
    fastcgi_param MM_ORG $geoip2_data_org;

    server {
        listen 8000;

        root /var/www;
        index index.php;


        ## only allow accessing the following php files
        location ~ ^/(index|matomo|piwik|js/index).php {
            fastcgi_pass unix:/var/run/php-fpm7.sock;
        }

        ## needed for HeatmapSessionRecording plugin
        location = /plugins/HeatmapSessionRecording/configs.php {
            fastcgi_pass unix:/var/run/php-fpm7.sock;
        }

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

        ## serve all other files normally
        location / {
            try_files $uri $uri/ =404;
        }

        ## disable all access to the following directories
        location ~ /(config|tmp|core|lang) {
            deny all;
            return 404;
        }
        location ~ /\.ht {
            deny  all;
            return 403;
        }

        location ~ \.(gif|ico|jpg|png|svg|js|css|htm|html|mp3|mp4|wav|ogg|avi|ttf|eot|woff|woff2|json)$ {
            allow all;
            ## Cache images,CSS,JS and webfonts for an hour
            ## Increasing the duration may improve the load-time, but may cause old files to show after an Matomo upgrade
            expires 1h;
            add_header Pragma public;
            add_header Cache-Control "public";
        }

        location ~ /(libs|vendor|plugins|misc/user) {
            deny all;
            return 403;
        }

        ## properly display textfiles in root directory
        location ~/(.*\.md|LEGALNOTICE|LICENSE) {
            default_type text/plain;
        }
    }
}

Using PHP Version 7.2.14, opcache

This seems to be a redis cache problem. We changed to using file cache and only running one instance of the UI container, and flushed all keys for the database used for matomo. Switched back to redis backend, scaled the UI containers to >1, and all errors went away.