I Installed Drupal 9.1.0 on a VPS running on Ubuntu 20.4 and using Nginx 1.18.0, with Composer 2.0.8.

I installed two modules (Token and Devel) and a theme (Barrio) using Composer. I can enable the modules without problem, but when I try to install Barrio and set it as default theme, I get an access denied error.

I tried to enable other themes, but I always get the same error.

I cleared the cache and installed the theme with Drush. Still, setting the theme as default one causes an error. In this case, the error in the log is the following one.

Path: /admin/appearance/default?theme=THEME_NAME&token=TOKEN. Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException: 'csrf_token' URL query argument is missing. in Drupal\Core\Routing\AccessAwareRouter->checkAccess() (line 117 of /PATHTOSITE/core/lib/Drupal/Core/Routing/AccessAwareRouter.php).

...:: UPDATE ::...

I used to start debugging to check where exactly the problem happens and realized that in line 57 of /Drupal/Core/Access/CsrfAccessCheck.php where code says :

$request->query->get('token', '')

there results become '' , which means $request->query->token is empty. debugging backward I looked at many files including :

Drupal/Core/Access/AccessArgumentsResolverFactory.php where it produces $request for csrfCheck
Drupal/Component/Utility/ArgumentsResolver.php which is used in above file

and finally I got to the place where problem is started:


this method can not recognize the token in url and cause all other problems, but I still don't know what cause this method failure

could it be my Nginx configurations?

Nginx config is:

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

One more thing , when I use dsm on request, token can be find in $request->requestUri (is shown as /admin/appearance/install?theme=stark&token=qI5_B99oNr4TWdNfu5aTdd01NezSpPKOZje8HCVOMEs), But $rquest->query["query_string"] is ""

1 Answer 1


Finally after spending few days doing debugging and searching I noticed that the error was due to my Nginx configuration , this configuration from Nginx website was what solved the problem for me:

server {
    server_name example.com;
    root /var/www/drupal/MYSITE/web;

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

    location = /robots.txt {
      allow all;
      log_not_found off;
      access_log off;

    # Very rarely should these ever be accessed outside of your lan
    location ~* \.(txt|log)$ {
      deny all;

    location ~ \..*/.*\.php$ {
      return 403;

    location ~ ^/sites/.*/private/ {
      return 403;

    # Block access to scripts in site files directory
    location ~ ^/sites/[^/]+/files/.*\.php$ {
      deny all;

    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
      allow all;

    # Block access to "hidden" files and directories whose names begin with a
    # period. This includes directories used by version control systems such
    # as Subversion or Git to store control files.
    location ~ (^|/)\. {
      return 403;

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

    location @rewrite {
      rewrite ^ /index.php;

    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
      deny all;
      return 404;

    # Protect files and directories from prying eyes.
    location ~* \. 
    (json|lock)|web\.config)$|/#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
      deny all;
      return 404;

    # In Drupal 8, we must also match new paths where the '.php' appears in
    # the middle, such as update.php/selection. The rule we use is strict,
    # and only allows this pattern with the update.php front controller.
    # This allows legacy path aliases in the form of
    # blog/index.php/legacy-path to continue to route to Drupal nodes. If
    # you do not have any paths like that, then you might prefer to use a
    # laxer rule, such as:
    #   location ~ \.php(/|$) {
    # The laxer rule will continue to work if Drupal uses this new URL
    # pattern with front controllers other than update.php in a future
    # release.
    location ~ '\.php$|^/update.php' {
      fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
      # Ensure the php file exists. Mitigates CVE-2019-11043
      try_files $fastcgi_script_name =404;
      # Security note: If you're running a version of PHP older than the
      # latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
      # See http://serverfault.com/q/627903/94922 for details.
      include fastcgi_params;
      # Block httpoxy attacks. See https://httpoxy.org/.
      fastcgi_param HTTP_PROXY "";
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PATH_INFO $fastcgi_path_info;
      fastcgi_param QUERY_STRING $query_string;
      fastcgi_intercept_errors on;
      # PHP 5 socket location.
      #fastcgi_pass unix:/var/run/php5-fpm.sock;
      # PHP 7 socket location.
      fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
      try_files $uri @rewrite;
      expires max;
      log_not_found off;

    # Fighting with Styles? This little gem is amazing.
    location ~ ^/sites/.*/files/styles/ {
      try_files $uri @rewrite;

    # Handle private files through Drupal. Private file's path can come
    # with a language prefix.
    location ~ ^(/[a-z\-]+)?/system/files/ {
      try_files $uri /index.php?$query_string;

    # Enforce clean URLs
    # Removes index.php from urls like www.example.com/index.php/my-page --> 
    # Could be done with 301 for permanent or other redirect codes.
    if ($request_uri ~* "^(.*/)index\.php/(.*)") {
      return 307 $1$2;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.