Skip to content

Introduced redis session handler does not work with non-root images #763

Description

@smueller18

In commit 83ea69d, a redis session handler was intrododuced. But the file /usr/local/etc/php/conf.d/redis-session.ini can not be written when using a non-root image, because the folder is owned by root and the default www-data user does not have write permission.

/var/www/html # ls -lha /usr/local/etc/php/conf.d
total 72
drwxr-xr-x    1 root     root        4.0K Feb 26 08:50 .
drwxr-xr-x    1 root     root        4.0K Feb 26 07:34 ..
-rw-r--r--    1 root     root          35 Feb 26 08:50 docker-php-ext-apcu.ini
-rw-r--r--    1 root     root          18 Feb 26 08:47 docker-php-ext-exif.ini
-rw-r--r--    1 root     root          16 Feb 26 08:47 docker-php-ext-gd.ini
-rw-r--r--    1 root     root          21 Feb 26 08:50 docker-php-ext-imagick.ini
-rw-r--r--    1 root     root          18 Feb 26 08:48 docker-php-ext-intl.ini
-rw-r--r--    1 root     root          18 Feb 26 08:48 docker-php-ext-ldap.ini
-rw-r--r--    1 root     root          23 Feb 26 08:50 docker-php-ext-memcached.ini
-rw-r--r--    1 root     root          82 Feb 26 08:48 docker-php-ext-opcache.ini
-rw-r--r--    1 root     root          19 Feb 26 08:48 docker-php-ext-pcntl.ini
-rw-r--r--    1 root     root          23 Feb 26 08:49 docker-php-ext-pdo_mysql.ini
-rw-r--r--    1 root     root          23 Feb 26 08:49 docker-php-ext-pdo_pgsql.ini
-rw-r--r--    1 root     root          19 Feb 26 08:50 docker-php-ext-redis.ini
-rw-r--r--    1 root     root          20 Feb 26 07:34 docker-php-ext-sodium.ini
-rw-r--r--    1 root     root          17 Feb 26 08:49 docker-php-ext-zip.ini
-rw-r--r--    1 root     root          18 Feb 26 08:50 memory-limit.ini
-rw-r--r--    1 root     root         189 Feb 26 08:50 opcache-recommended.ini

For running my nextcloud image, I use the default non-privileged user www-data.

/var/www/html $ id
uid=82(www-data) gid=82(www-data) groups=82(www-data)

Activity

  1. added 2 commits that reference this issue on May 30, 2019
    813c5e9
    2522037
  2. smueller18 commented on May 30, 2019

    @smueller18
    Author

    Here is a command to reproduce this behavior:

    » docker run --rm -it --user www-data nextcloud:fpm-alpine sh
    /var/www/html $ NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=admin NEXTCLOUD_UPDATE=1 REDIS_HOST=localhost /entrypoint.sh sh -c 'echo $?'
    Configuring Redis as session handler
    /entrypoint.sh: line 26: can't create /usr/local/etc/php/conf.d/redis-session.ini: Permission denied
    Initializing nextcloud 16.0.1.1 ...
    Initializing finished
    New nextcloud instance
    running web-based installer on first connect!
    
  3. changed the title [-]Introduced redis session handler breaks entrypoint of non-root images[/-] [+]Introduced redis session handler does not work with non-root images[/+] on May 30, 2019
  4. Unb0rn commented on Jun 2, 2019

    @Unb0rn

    Any workarounds? I have this problem too

  5. J0WI commented on Jun 4, 2019

    @J0WI
    Contributor

    Thanks for reporting this. I can reproduce the issue, but I'm not very happy with the proposed permission change in #764.
    However, this should not break your NextCloud, you just have no Redis session handler. As a workaround, you can mount a redis-session.ini manually.

  6. smueller18 commented on Jun 4, 2019

    @smueller18
    Author

    I know changing the permissions is kind of a security issue. But running the image as root is even more.

    What definitely does not work:

    1. Writing configuration in .user.ini file. According to PHP memory limit to 128 ? #447 (comment), this does not work for CLI mode.

    Here are some options that I figured out:

    1. Change ownership of /usr/local/etc/php/conf.d/ to user www-data. The entrypoint creates redis-session.ini. Results in security concerns.
    2. Create placeholder file /usr/local/etc/php/conf.d/redis-session.ini in the Dockerfile with no content. Change ownership of only /usr/local/etc/php/conf.d/redis-session.ini to user www-data. Content can be written in the entrypoint. More secure than 1. but if redis is not used, there is an empty file.
    3. Create file /usr/local/etc/php/conf.d/session.ini in the Dockerfile with defaults from default config /usr/local/etc/php/php.ini-production. Change ownership of only /usr/local/etc/php/conf.d/session.ini to user www-data. Content can be replaced with sed in the entrypoint. Security equal to 2. but duplicate config for session.
    4. Only write /usr/local/etc/php/conf.d/redis-session.ini when running as root, otherwise log a warning that the file must be mounted manually instead of throwing the error like described above. No security concerns.

    Currently I think version 3 is the best trade-off. @J0WI what do you think?

  7. J0WI commented on Jun 5, 2019

    @J0WI
    Contributor

    Why would you prefer 3 over 2?
    4 would also be an option for me, this is basically what happens now.

    @tilosp what is your opinion?

  8. smueller18 commented on Jun 7, 2019

    @smueller18
    Author

    @J0WI I prefer 3 over 2 because if redis host is not set, there is a file called redis-session.ini which might irritate users.
    Best practices of writing Docker images says

    If a service can run without privileges, use USER to change to a non-root user.
    https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user

    All images in this project should run as non-root by default. Having this in mind, 4 is not an option. I will open a new PR that sets non-root users as default for all used images and examples. Not choosing 4 is a precondition for that.

  9. tilosp commented on Jun 12, 2019

    @tilosp
    Member

    1 and 2 are not really an option because it wouldn't work for users other than root and www-data. Instead we would need to chmod 777 them.

    It there any difference between 1, 2 and 3 in terms of security? In each case php can modify it's own config.

    All images in this project should run as non-root by default. Having this in mind, 4 is not an option. I will open a new PR that sets non-root users as default for all used images and examples. Not choosing 4 is a precondition for that.

    The web server already runs as a non-root user (www-data) by default.
    And i don't see how adding USER www-data to the Dockerfile will improve the security. If you don't trust the container to run apache as www-data you also can't trust the default user to be www-data.

  10. J0WI commented on Jun 12, 2019

    @J0WI
    Contributor

    It there any difference between 1, 2 and 3 in terms of security?

    Not it terms of security, but I'd avoid changing upstream files.

  11. smueller18 commented on Jun 12, 2019

    @smueller18
    Author

    The web server already runs as a non-root user (www-data) by default.

    I can't verify that, have a look at the commands:

    • FPM
    » docker run --rm -it --name nextcloud -d nextcloud:fpm-alpine
    b24abcf965ff7b7027bfa2d6e7295898e368ea9aba2a56332c301a3b1a7ad267
    
    » docker exec -i -t nextcloud ps aux                          
    PID   USER     TIME  COMMAND
        1 root      0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
       35 www-data  0:00 php-fpm: pool www
       36 www-data  0:00 php-fpm: pool www
       37 root      0:00 ps aux
    
    • Apache
    » docker run --rm -it --name nextcloud -d nextcloud
    7f64940a1f4db036e821432d4a53ba937a32ae5a25093a94ad09073348f29e16
    
    » docker exec -i -t nextcloud ps aux               
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  3.8  0.4 496432 35640 pts/0    Ss+  20:07   0:00 apache2 -DFOREG
    www-data    45  0.0  0.1 496464  9380 pts/0    S+   20:07   0:00 apache2 -DFOREG
    www-data    46  0.0  0.1 496464  9380 pts/0    S+   20:07   0:00 apache2 -DFOREG
    www-data    47  0.0  0.1 496464  9380 pts/0    S+   20:07   0:00 apache2 -DFOREG
    www-data    48  0.0  0.1 496464  9380 pts/0    S+   20:07   0:00 apache2 -DFOREG
    www-data    49  0.0  0.1 496464  9380 pts/0    S+   20:07   0:00 apache2 -DFOREG
    root        50 22.0  0.0  36632  2804 pts/1    Rs+  20:07   0:00 ps aux
    

    The PID 1 process always runs as root by using the defaults.

    And i don't see how adding USER www-data to the Dockerfile will improve the security. If you don't trust the container to run apache as www-data you also can't trust the default user to be www-data.

    Have a look at the following blog post. There is a good explanation, why running images as root is not a good practice:

    Containers that run as root frequently have far more permissions than their workload requires which, in case of compromise, could help an attacker further their attack.
    https://kubernetes.io/blog/2018/07/18/11-ways-not-to-get-hacked/#8-run-containers-as-a-non-root-user.

    If the USER directive is set to www-data the attack vector is minimized when running with defaults. And if anyone wants to run the image as root this is still possible by manually changing the user.

    1 and 2 are not really an option because it wouldn't work for users other than root and www-data. Instead we would need to chmod 777 them.

    It makes no sense to use another user than root or www-data to run fpm or apache, other users do not have sufficient permissions.

    How should we go an with this issue?

  12. tilosp commented on Jun 12, 2019

    @tilosp
    Member

    I can't verify that, have a look at the commands:

    Yes the entrypoint scipt and apache/fpm master process run as root by default. I meant that the processes that handle the requests and run the php code are run by a different user.

    If the USER directive is set to www-data the attack vector is minimized when running with defaults. And if anyone wants to run the image as root this is still possible by manually changing the user.

    Yes it will reduce the attack surface in case there is a bug in apache/fpm that allows code execution in the master process. But it won't help in the case of a malicious docker image. To protect against a malicious docker image you would need to override to default user for example in the docker-compose file.

    Something like this should be done upstream in the php base image, then all php based images get the benefit.

    It makes no sense to use another user than root or www-data to run fpm or apache, other users do not have sufficient permissions.

    This is valid use case and it is supported by the upstream image. It should work, have you tried it? If there is a permission problem it is a bug.

  13. smueller18 commented on Jun 22, 2019

    @smueller18
    Author

    I was not aware of that in docker-library/php#787 the permissions of folder /var/www/html were set to 777. So yes, every user can be used to run the php-fpm image.

    I guess the upstream images will not set the user directive in the mid-term because that will break too many existing child images. So in my opinion, this should be treated proactive from the application side.

    After the discussions above, how should we follow?
    In the meantime, I am just happy if the entrypoint does not throw an exception anymore. And all following changes of this project should work user independent so that updating the image is always possible without manual testing in a sandbox environment.

  14. 23 remaining items

  15. phate7 commented on Feb 13, 2024

    @phate7

    I work around this by mounting redis-session.ini manually as a volume, i.e. touch ./nextcloud/redis/redis-session.ini and then add this flag to podman/docker on the nextcloud container:

    --volume ./nextcloud/redis/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:z
    

    Having the exact same problem. Thank you for the fix. Nextcloud should up their game on how to correctly use docker and providing images. The whole upgrade process with nextcloud is also horrendous. If there would be a viable alternative I would gladly take it.

  16. hypery2k commented on Apr 19, 2024

    @hypery2k
  17. LorbusChris commented on May 5, 2024

    @LorbusChris

    OT: You may want to check out https://github.com/LorbusChris/nextcloud-quadlet for an alternative way of running a rootless setup. The stack is a bit different, using Valkey instead of Redis, and Envoy as proxy. I've been running this setup for ~4 months with automatic upgrades and without any issues.

  18. added and removed on May 13, 2024
  19. J0WI commented on May 13, 2024

    @J0WI
    Contributor

    will this ever be fixed?

    Unlikely, due the reason mentioned in #763 (comment)
    Making the server config world writeable would make the image less secure for everyone. Mounting a custom config is the way to go.

  20. guillaumedsde commented on Mar 22, 2025

    @guillaumedsde

    Hello 👋

    For people running a docker Nextcloud instance with a read-only root filesystem and a non-root user, I have found a workaround to this issue.

    PHP allows you to change the directories in which it is going to look for ini configurations using the environment PHP_INI_SCAN_DIR variable.
    So you can launch the container with PHP_INI_SCAN_DIR="/tmp/php_conf_d:" assuming you have mounted a tmpfs in /tmp (note that the trailing : tells PHP to append the specified directory to the existing set of PHP ini scan directories).
    Then you can mount a custom script in /docker-entrypoint-hooks.d/before-starting/redis_config_generator.sh which could look something like this:

    #!/usr/bin/env bash
    
    set -euo pipefail
    
    install -D --mode=600 <(cat <<EOF
    session.save_handler = redis
    session.save_path = "tcp://${HOOK_REDIS_HOST}:6379?auth=${HOOK_REDIS_HOST_PASSWORD}"
    redis.session.locking_enabled = 1
    redis.session.lock_retries = -1
    redis.session.lock_wait_time = 10000
    EOF
    ) "/tmp/php_conf_d/redis-session.ini"

    (Note that the redis environment variables are prefixed with HOOK_ to avoid Nextcloud attempting to generate its own configuration on top of yours using the REDIS_ environment variables)

    This should enable you to have a custom Redis configuration readable by your custom --user and written to a writable location on the filesystem.

  21. antoinetran commented on Mar 25, 2025

    @antoinetran

    Hi, I have the same issue in a Kubernetes environment, using Nextcloud helm chart, in OpenShift with restrictive non root policy.

    This is a workaround that works!! I couldn't mount an emptyDir directly to the /usr/local/etc/php/conf.d, because that would have overriden the existing file. So I created an emptyDir, and initContainer to create en empty file, what a workaround!!!

    nextcloud:
      extraInitContainers:
        # create empty redis file for nextcloud entrypoint to override it
        - name: init-redis-session-ini
          image: busybox
          command: ['touch', '/usr/local/etc/php/conf.d/redis-session.ini']
          volumeMounts:
            - name: nextcloud-redis-session-ini
              mountPath: "/usr/local/etc/php/conf.d"
    
      extraVolumes:
      - name: nextcloud-redis-session-ini
        emptyDir: {}
      extraVolumeMounts:
      - name: nextcloud-redis-session-ini
        mountPath: "/usr/local/etc/php/conf.d/redis-session.ini" # fix permission denied error
        subPath: redis-session.ini
    
  22. Aetylus commented on Apr 10, 2025

    @Aetylus

    Thank you, that helped me fix my issue with the Helm chart as well. Just a note for anyone else that finds this issue trying to run as non-root along with the cronjob container, you have to set the securityContext on the init-redis-session-ini initContainer. podSecurityContext won't work because the cronjob container must run as root to work properly.

    There is an open pull request (nextcloud/helm#703) to use a k8s CronJob resource instead which would solve this problem, in which case podSecurityContext should work if it's accepted.

  23. antoinetran commented on Jun 17, 2025

    @antoinetran

    For information, this is the PullRequest I created that fixed this issue: nextcloud/helm#717 . One day it will be merged!

  24. agravgaard commented on Jan 15, 2026

    @agravgaard

    If I'm understanding nextcloud/helm#752 correctly, then this could currently be worked around by adding to the values:

    nextcloud:
      extraVolumes:
        - name: php-confd
          emptyDir: {}
        - name: tmpfs
          emptyDir: {}
      extraVolumeMounts:
        - name: php-confd
          mountPath: "/usr/local/etc/php/conf.d/redis-session.ini"
          subPath: redis-session.ini
        - name: tmpfs
          mountPath: "/tmp"
      extraInitContainers:
        - name: init-redis-session-ini
          image: mirror.gcr.io/alpine:latest
          command: ['touch', '/usr/local/etc/php/conf.d/redis-session.ini']
          volumeMounts:
            - name: php-confd
              mountPath: "/usr/local/etc/php/conf.d"

    This seems to work for me, but my testing has been limited so far.

    (But my values file is already about 300 lines long and hard to maintain, so I'd prefer to get the above-mentioned fix merged)

    Edit: a writable '/tmp' was also needed.

  25. joshtrichards commented on Jan 29, 2026

    @joshtrichards
    Member

    Here's a thought:

    • Have the entrypoint script compute the complex Redis session save path (as it basically does currently) but instead of writing it out to an INI file, we export it as an environment variable.
    • Have our php.ini (or an included ini file - in our case probably nextcloud.ini) reference the environment variable(s) directly. **We already do this for PHP_MEMORY_LIMIT though it doesn't have any entrypoint logic since it's much simpler).

    Specifically:

    • we drop redis-session.ini file entirely (no write required in the entrypoint)
    • instead of having the entrypoint's Redis config logic create the file, we have it build the appropriate session.save_path value just before launching apache/fpm and export it into an environment variable (e.g. PHP_REDIS_SESSION_SAVE_PATH). Also include others like PHP_REDIS_SESSION_HANDLER = "redis" and the locking settings
    • In nextcloud.ini we reference the environment variables just like we do for PHP_MEMORY_LIMIT/etc.

    This should work.

    Refs:

    • Current REDIS* config handling:

      docker/docker-entrypoint.sh

      Lines 115 to 148 in 0af85f2

      if [ -n "${REDIS_HOST+x}" ]; then
      echo "Configuring Redis as session handler"
      {
      file_env REDIS_HOST_PASSWORD
      echo 'session.save_handler = redis'
      # check if redis host is an unix socket path
      if [ "$(echo "$REDIS_HOST" | cut -c1-1)" = "/" ]; then
      if [ -n "${REDIS_HOST_PASSWORD+x}" ]; then
      if [ -n "${REDIS_HOST_USER+x}" ]; then
      echo "session.save_path = \"unix://${REDIS_HOST}?auth[]=${REDIS_HOST_USER}&auth[]=${REDIS_HOST_PASSWORD}\""
      else
      echo "session.save_path = \"unix://${REDIS_HOST}?auth=${REDIS_HOST_PASSWORD}\""
      fi
      else
      echo "session.save_path = \"unix://${REDIS_HOST}\""
      fi
      # check if redis password has been set
      elif [ -n "${REDIS_HOST_PASSWORD+x}" ]; then
      if [ -n "${REDIS_HOST_USER+x}" ]; then
      echo "session.save_path = \"tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}?auth[]=${REDIS_HOST_USER}&auth[]=${REDIS_HOST_PASSWORD}\""
      else
      echo "session.save_path = \"tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}?auth=${REDIS_HOST_PASSWORD}\""
      fi
      else
      echo "session.save_path = \"tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}\""
      fi
      echo "redis.session.locking_enabled = 1"
      echo "redis.session.lock_retries = -1"
      # redis.session.lock_wait_time is specified in microseconds.
      # Wait 10ms before retrying the lock rather than the default 2ms.
      echo "redis.session.lock_wait_time = 10000"
      } > /usr/local/etc/php/conf.d/redis-session.ini
      fi
    • Current PHP.ini handling w/ environment variable substitution (used for PHP_MEMORY_LIMIT):
      # set recommended PHP.ini settings
      # see https://docs.nextcloud.com/server/latest/admin_manual/installation/server_tuning.html#enable-php-opcache
      RUN { \
      echo 'opcache.enable=1'; \
      echo 'opcache.interned_strings_buffer=32'; \
      echo 'opcache.max_accelerated_files=10000'; \
      echo 'opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION}'; \
      echo 'opcache.save_comments=1'; \
      echo 'opcache.revalidate_freq=60'; \
      echo 'opcache.jit=1255'; \
      echo 'opcache.jit_buffer_size=8M'; \
      } > "${PHP_INI_DIR}/conf.d/opcache-recommended.ini"; \
      \
      echo 'apc.enable_cli=1' >> "${PHP_INI_DIR}/conf.d/docker-php-ext-apcu.ini"; \
      \
      { \
      echo 'apc.serializer=igbinary'; \
      echo 'session.serialize_handler=igbinary'; \
      } >> "${PHP_INI_DIR}/conf.d/docker-php-ext-igbinary.ini"; \
      \
      { \
      echo 'memory_limit=${PHP_MEMORY_LIMIT}'; \
      echo 'upload_max_filesize=${PHP_UPLOAD_LIMIT}'; \
      echo 'post_max_size=${PHP_UPLOAD_LIMIT}'; \
      } > "${PHP_INI_DIR}/conf.d/nextcloud.ini"; \
    • Official PHP docs on php.ini environment variable support: https://www.php.net/manual/en/configuration.file.php#configuration.file

    Proposed (untested):

    1. Add this logic to our entrypoint before PHP or the webserver runs:
    if [ -n "${REDIS_HOST+x}" ]; then
        # shell function to load REDIS_HOST_PASSWORD from file, if using Docker secrets
        file_env REDIS_HOST_PASSWORD
    
        if [ "$(echo "$REDIS_HOST" | cut -c1-1)" = "/" ]; then
          # Unix socket path
          if [ -n "${REDIS_HOST_PASSWORD+x}" ]; then
            if [ -n "${REDIS_HOST_USER+x}" ]; then
              FINAL_SAVE_PATH="unix://${REDIS_HOST}?auth[]=${REDIS_HOST_USER}&auth[]=${REDIS_HOST_PASSWORD}"
            else
              FINAL_SAVE_PATH="unix://${REDIS_HOST}?auth=${REDIS_HOST_PASSWORD}"
            fi
          else
            FINAL_SAVE_PATH="unix://${REDIS_HOST}"
          fi
        elif [ -n "${REDIS_HOST_PASSWORD+x}" ]; then
          # TCP with password
          if [ -n "${REDIS_HOST_USER+x}" ]; then
            FINAL_SAVE_PATH="tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}?auth[]=${REDIS_HOST_USER}&auth[]=${REDIS_HOST_PASSWORD}"
          else
            FINAL_SAVE_PATH="tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}?auth=${REDIS_HOST_PASSWORD}"
          fi
        else
          # TCP without password
          FINAL_SAVE_PATH="tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}"
        fi
    
        # Export as environment variable for PHP-FPM/apache
        export PHP_REDIS_SESSION_SAVE_PATH="$FINAL_SAVE_PATH"
    fi
    
    # Optionally also set other session handler options
    export PHP_REDIS_SESSION_HANDLER="redis"
    export PHP_REDIS_SESSION_LOCKING_ENABLED=1
    export PHP_REDIS_SESSION_LOCK_RETRIES=-1
    export PHP_REDIS_SESSION_LOCK_WAIT_TIME=10000
    
    # exec the CMD as usual at the end of your entrypoint script:
    exec "$@"
    
    1. Make our nextcloud.ini reference the above environment variables (i.e. add these to our Dockerfile-*.template files:
    ; Use Redis for PHP session handler if configured
    session.save_handler = "${PHP_REDIS_SESSION_HANDLER}"
    session.save_path = "${PHP_REDIS_SESSION_SAVE_PATH}"
    redis.session.locking_enabled = ${PHP_REDIS_SESSION_LOCKING_ENABLED}
    redis.session.lock_retries = ${PHP_REDIS_SESSION_LOCK_RETRIES}
    redis.session.lock_wait_time = ${PHP_REDIS_SESSION_LOCK_WAIT_TIME}
    

    In PHP 8.3+. we can even add clear fallbacks per PHP docs (though may be unnecessary since defaults are likely fine -- or we could handle it in our entrypoint logic too). Something like this I believe:

    session.save_handler = "${PHP_REDIS_SESSION_HANDLER:-'files'}"
    
  26. added a commit that references this issue on Mar 23, 2026
    5be47ac
  27. joshtrichards commented on Mar 23, 2026

    @joshtrichards
    Member

    Early prototype of approach that eliminates entrypoint needing to modify redis-session.ini file is in draft PR #2550. Needs testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    integration: memcacheIntegration with any Nextcloud supported Memcached (Redis, Memcached, etc)rootlessRunning in Docker w/o rootuserRunning containers under a different userwontfix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions