Problem/Motivation

If your Drupal site is installed on a shared hosting environment, you need to somehow tell the server that Drupal's web root is (normally) in the /web subdirectory and not /home/my_user/public_html.

On shared hosting you often cannot update Apache settings. If you cannot also Set DocumentRoot via settings a popular solution is to Redirect with an .htaccess file to the /web subdirectory, and also add a few $GLOBALS['request']'s in settings.php.

This has worked well until Drupal 11.4, where it stopped working for logged in users, who may get this error:

Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.

... doing certain actions, like accessing the /user and /user/login paths, or logging out. Admin tasks such as
running Cron via /admin/reports/status/run-cron are affected, as well as others:

Right now, the site works fine for anonymous users—no errors on reaching the homepage, or any other content. I can also access remotely via Drush commands. I do get the redirect error when attempting these tasks:

  • Login via drush uli—but all looks OK in the CLI, only when pasting the string in the browser is where it happens
  • Logout via command in Admin Toolbar
  • Checking available updates manually at /admin/reports/updates

From #3611033: “Redirects to external URLs are not allowed by default” using “check manually” on available updates report.

Originally reported in 2015:
As a front-end user, I have downloaded D8 RC3 locally and installed Drupal no problems.

  • Create an article (a simple "about us" page)
  • SAVE
  • Message returned on blank screen "Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it."

Yes, I can then back track and the page is saved but any new Save or Resave returns the same message.
I did a little searching and found the appropriate file under the core folders "TrustedRedirectResponse.php" but being a front end user, what needs to be changed to bypass this message? Much of the responses are technical for those at that level, but I (and probably others) need a simple solution please.

I have tried inserting http://localhost..... into various spots with no result (the http seems valid but anything after that is greyed out).

I am not a PHP developer. Is there something I need/can do to make this message 'go away"? Is this on the 'to-do' list to be fixed?
Thank you in advance!

Steps to reproduce

  1. Install a fresh drupal/recommended-project project in DDEV with relocated web root, and get everything up and running
  2. Update DDEV to serve from the project root of the installation by updating .ddev/config.yaml to use docroot: "" and webserver_type: apache-fpm
  3. Add a .htaccess file with redirect to /web per Redirect with .htaccess file
  4. Rebuild caches and see the error doing these tasks:
    • Login via drush uli—but all looks OK in the CLI, only when pasting the string in the browser is where it happens
    • Logout via Admin Toolbar
    • Flush caches via Admin Toolbar
    • Run Cron via /admin/reports/status/run-cron
    • Checking available updates manually at /admin/reports/updates

Proposed resolution

Add support for Drupal installations using a relocated web root on cPanel or cPanel-like hosting providers, where web root cannot be configured in Apache, or set via DocumentRoot in the web host GUI, so .htaccess redirect is the only available method. Either directly in Drupal core, or by adding support for a simple configuration method.

Add a test which verifies that the method works.

Workaround and future options

Situation July 2026: Stay on Drupal 11.3 for now, it is supported until December 2026.

Drupal 11.3 and before
We can document in Relocated web root on shared hosting how to serve Drupal from a relocated web root, redirecting internally to /web folder with a .htaccess file and setting settings.php to handle LocalRedirectResponse to pass base_url validation.

Drupal 11.4
If you already upgraded to Drupal 11.4, you can attempt to restore to 11.3 from a backup, if you made one.

A possible workaround, adopted from the original, is in comment 106.

Future options
If it is not solved before Drupal 11.3 EOL in December 2026, consider switching to shared hosting which allows you to easily Set DocumentRoot via settings. See Hosting for small and medium sized projects doc page for affordable options, which support managing Drupal with Composer and to easily set /web as root via the GUI.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-2612160

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

  • 2612160-webroot-workaround Comparecompare
  • 1 hidden branch
  • main Comparecompare

Comments

miniwebs2 created an issue. See original summary.

tim.plunkett’s picture

Can you explain the "http://localhost" part more?
Is there a ?destination= in the URL at any point during article creation?

miniwebs2’s picture

The localhost bit is just the destination on my local server eg http://localhost:8888/drupal8/node/1/edit.
When I hit "save and keep published" (and msg comes up as above) that's the URL and when I go back a page in my browser it also has the same URL but the content has been saved.
My URL Path settings are "/about-us" (I gather D8 uses a forward slash now.)
And no, there is no ? in destination URL so clean URLS are working.
I even just did a time zone configuration change and when Saving that - same message.

tim.plunkett’s picture

If you could help debug this, I'd appreciate it. I understand that you're not a developer, but if you could try the following, that'd be great:

In the file core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php, around line 90, insert this line:
debug($response->getTargetUrl());

This can be anywhere after that large docblock on line 83, as long as its before the " $safe_response = new Response($message, 400);" part on line 91.

Ideally you should see an external URL in a little message at the top of your screen the next time you submit an article.

If that doesn't work, try $message = $response->getTargetUrl(); right before the trigger_error() line.

$response->getTargetUrl() will show us the offending redirect URL that is giving you trouble.

miniwebs2’s picture

2nd method worked: Message shown now (after an edit and 'save and keep published') as "http://localhost:8888/drupal8/about-us"
Create a new article and Save: Message how reads: "http://localhost:8888/drupal8/node/7"
Are we any further ahead?? :)
Seems the offending redirect URL is the actual url I'm working on.

tim.plunkett’s picture

Title: Saving a Drupal 8 article returns a message containing "Drupal\Core\Routing\TrustedRedirectResponse" » Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber
Version: 8.0.0-rc3 » 8.0.x-dev
Category: Support request » Bug report

So the http:://localhost:8888/drupal8 part is not being recognized as being the same trusted site, when checked during the redirects. I've definitely installed D8 in a subdir before, so it could be either the localhost or the :8888 part throwing things off.

Short term, can you install to a named URL? Like "drupal8.dev" instead? That would let you progress while we fix the bug.

miniwebs2’s picture

Okay - here's a thing - have just run up another site EXACTLY the same configuration etc on exactly the same server the same day, the same user login details, same 2 pages on the site, etc - and it's good.
I can save and edit pages no problems.
Trying to recreate the issue but can't .... mmmmm...... if I do re-create the issue, I'll get back to here and repost but some sort of weird glitch in the install???
Actually now that I think that - a couple of my toolbar icons were greyed out as well when I went to insert an image - the image icon I found by hovering over the greyed out icon. But in general there was nothing wrong when installing and all went as it should have.

tim.plunkett’s picture

Well I'm glad to hear things are working. I'm going to leave this open for a bit though, please comment here if it happens again.

miniwebs2’s picture

Appreciate your assistance here Tim - I had noticed a couple of other issues similar in other threads from a while back so maybe there is something amiss?
Leave it with you for now - I'm only testing D8 anyway and trying to get my head around all the new stuff.

dawehner’s picture

So yeah one thing which certainly exists is that our caching is not host / base URL / port aware, so if you have rendered the page one using for example d8.dev vs. localhost/d8, you have some really weird issues, so better clear all caches before doing anything.

miniwebs2’s picture

Update:
There's now an RC 4 version of Drupal out so trashed all previous RC 3 versions, ran up another local install using RC4.
Install ran fine (except that OPCache needed to be reset in php.ini as it did before but haven't previously mentioned that one!)
Running PHP 5.6.1 on MAMP.

What I did: Changed the Article Content Type to non front page and no user info shown - SAVE, next screen same as per issues above:
"Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it."

'SAVE" seems to trigger the above message, if I go back a browser page, the info has actually saved correctly and I can carry on until I want to SAVE or SAVE and PUBLISH something again - same error message.

This is why I'm not liking Drupal 8 yet! Can't get past these glitches.

miniwebs2’s picture

Have now installed locally the first release of Drupal 8 - same issue.
Just keeping you updated on my concerns here .... :)
Have also just installed on a remote server - no problems there so this issue only affects local installs for me.

miniwebs2’s picture

Version: 8.0.x-dev » 8.0.0

Drupal 8.0.0
Local install still creates the issue message.
Remote install works fine.

tim.plunkett’s picture

Version: 8.0.0 » 8.0.x-dev

Seems like an environment specific bug. Not sure what the exact factor is.

Max Scott’s picture

I have a similar issue. Often, when I have inputted data, I get the message, "Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it."

As indicated above, this message is generated by core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php when an InvalidArgumentException is caught in line 83. It appears that the exception is generated by the statement in line 79
$safe_response = LocalRedirectResponse::createFromRedirectResponse($response);

This method of Drupal/Core/Routing/LocalRedirectResponse.php is inherited from Drupal/Component/HttpFoundation/SecuredRedirectResponse.php.

In the class SecuredRedirectResponse, the code for this method creates a new LocalDirectResponse instance by the statement
$safe_response = new static($response->getTargetUrl(), $response->getStatusCode(), $response->headers->allPreserveCase());
However, the class LocalDirectResponse does not contain or inherit a constructor with these arguments.

Could this be the source of the problem?

Max Scott’s picture

The explanation given in my previous comment is not correct. Drupal\Core\Routing\LocalRedirectResponse indirectly extends Symfony\Component\HttpFoundation\RedirectResponse which contains a constructor with those arguments.

Issue

However, I have installed Drupal 8.0.1 in a subdirectory 'drupal' of my web root directory. I have modified the .htaccess file in the web root directory by adding the following lines of code, so that I can remove references to the 'drupal' directory in my URLs:

# Serve Drupal 8 from sub directory in web root
RewriteRule ^$ drupal/index.php [L]
RewriteCond %{DOCUMENT_ROOT}/drupal%{REQUEST_URI} -f
RewriteRule .* drupal/$0 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* drupal/index.php?q=$0 [QSA]

Whenever I add data (for example, by way of editing a page, etc.) I get a screen with just the line, "Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it."

Current workaround

I have modified the class Drupal\Core\EventSubscriber\RedirectResponseSubscriber by replacing the occurrences of LocalRedirectResponse with TrustedRedirectResponse as the following patch shows.

--- a/<html>RedirectResponseSubscriber.php (<b>Today 6:47:33 AM</b>)</html>
+++ b/<html><b>Current File</b></html>
@@ -9,7 +9,7 @@
 use Drupal\Component\HttpFoundation\SecuredRedirectResponse;
 use Drupal\Component\Utility\UrlHelper;
-use Drupal\Core\Routing\LocalRedirectResponse;
+use Drupal\Core\Routing\TrustedRedirectResponse;
 use Drupal\Core\Routing\RequestContext;
 use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
@@ -76,7 +76,7 @@
           // concrete implementation. Default to LocalRedirectResponse, which
           // considers only redirects to within the same site as safe.
-          $safe_response = LocalRedirectResponse::createFromRedirectResponse($response);
+          $safe_response = TrustedRedirectResponse::createFromRedirectResponse($response);
           $safe_response->setRequestContext($this->requestContext);
         }

So far this has solved the issue, but I am not sure whether or not this might have security implications.

Max Scott’s picture

The above explanation is incorrect. Drupal\Core\Routing\LocalRedirectResponse class indirectly extends Symfony\Component\HttpFoundation\RedirectResponse which has a constructor with those arguments.

Issue

I have installed Drupal 8.0.1 in a sub directory, 'drupal', of my web root directory, and have altered the .htaccess file in the web root directory so that I can access Drupal without referencing the 'drupal' directory in my URLs. The changes are as follows:

# Serve Drupal 8 from sub directory in web root
RewriteRule ^$ drupal/index.php [L]
RewriteCond %{DOCUMENT_ROOT}/drupal%{REQUEST_URI} -f
RewriteRule .* drupal/$0 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* drupal/index.php?q=$0 [QSA]

Whenever I add data to the website (for example by editing a node) I get just the line "Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it." on an otherwise blank page.

Work around

I have modified the Drupal\Core\EventSubscriber\RedirectResponseSubscriber class by changing LocalRedirectResponse to TrustedRedirectResponse as the following patch indicates.

--- a/<html>RedirectResponseSubscriber.php (<b>Today 6:47:33 AM</b>)</html>
+++ b/<html><b>Current File</b></html>
@@ -9,7 +9,7 @@
 use Drupal\Component\Utility\UrlHelper;
-use Drupal\Core\Routing\LocalRedirectResponse;
+use Drupal\Core\Routing\TrustedRedirectResponse;
 use Drupal\Core\Routing\RequestContext;
@@ -76,7 +76,7 @@
           // considers only redirects to within the same site as safe.
-          $safe_response = LocalRedirectResponse::createFromRedirectResponse($response);
+          $safe_response = TrustedRedirectResponse::createFromRedirectResponse($response);
           $safe_response->setRequestContext($this->requestContext);

This seems to have solved the issue, but I do wonder whether or not this change might have security implications.

gábor hojtsy’s picture

shotsy247’s picture

I am receiving this error as well.

I have set up a multisite version of D8 within a subdirectory.

For example it is installed at root/public_html/drupal/

I have an htaccess file pointing all traffic to the drupal directory.

When I access my site at www.example.com I do see the site and all content just fine.

But when I attempt to log in I am given the error:

Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.

If I add drupal to the url I am allowed to log in, for example www.example.com/drupal/

I attempted the workaround suggested in #17, which removes the error, but now says my user1 is denied access.

UPDATE: The access denied error was actually a result of stored cookies on my browser. Attempting the same log in via a new browser resulted in success. Now hopefully these changes will not result in security issues.

_t

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

stephen Piscura’s picture

I have an identical setup to Max Scott (Drupal in a subdirectory in a shared hosting environment) and his patch in #17 eliminated the behavior. Cheers to you Max Scott!

As you say though, i too am curious about the security implications.

vayu’s picture

I also have the message on various pages when I run drupal (8.1.1) in a subdirectory:
"Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it."

I applied the patch from comment #17 and it seems to have eliminated it. I'm worried though because I don't want to play with core. If not just because I don't want to remember what I've done, let alone security. Any other ideas or news on this issue? What's the preferred way to run Drupal 8 in a subdirectory?

* Update, adding the lines given in #30 below to settings.php works for me (and of course feel a lot better than messing with core).

justinlevi’s picture

Here's an actual patch file for #17 above so I can include this in my composer.json file.

justinlevi’s picture

StatusFileSize
new1.1 KB

sorry, adding again as a .patch file.

dave.mikolaj’s picture

StatusFileSize
new1.47 KB

Hi All,

New to Drupal, and came across this issue as well on our local development/uat boxes - the issue is that the URLs aren't always external - if you access the instance via a PathAlias or on a different domain, this error will occur.

Post 17 by Max fixed this issue for us, however there didn't seem to be a patch file made for it, so I've made the changes as described, tested it and this has fixed our issue. Thanks to all who helped with this issue.

brianV’s picture

Status: Active » Needs review

Moving to CNR due to the patch.

Status: Needs review » Needs work

The last submitted patch, 25: core-use-trustedredirect-2612160-17-d8.patch, failed testing.

The last submitted patch, 25: core-use-trustedredirect-2612160-17-d8.patch, failed testing.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andrej galuf’s picture

This is a very, very bad idea. There's a good reason why all user input data is handled by LocalRedirectResponse. Replacing it with a TrustedRedirect response is like blowing up the front door to ensure the burglar won't break through them.

The problem actually originates in the .htaccess redirect itself. The redirect forwards all calls to a subdirectory, but in so doing changes the $base_url to domain.com/subdirectory. LocalRedirectResponse then tries to validate the base_url, using \Drupal\Component\Utility\UrlHelper::externalIsLocal(), which in turn checks whether either the base_url's path is / (that's no longer the case due to redirect) or the first path is equal to expected url (again, it is not, as the /subdirectory is new).

This single method is the reason we have a failing test here and this single method is also the reason why we do not want to blow up the door.

So let's trail back a bit and look at the big picture:
Our expected domain is at domain.com. Drupal sends the request to itself there, the request is forwarded to Drupal because it's not located where Drupal expects itself to be and as a result fails validation. Simplest solution?

Add the following snippet to settings.php:

if (isset($GLOBALS['request']) and
'/subdirectory/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

Replace subdirectory with your subdirectory name. What does this do? It basically tells Drupal to throw out the subfolder directory in the request, which makes it pass the base_url validation again.

There, done and the doors are still whole.

JoshOrndorff’s picture

@Andrej Galuf,

Thanks for your helpful info on why the path in #17 is a bad idea. I've used your code in #30 instead and found that it works as I had hoped.

Note to anyone else who is trying it be sure to clear your local browser cookies while setting this up. I'm not exactly sure what session info gets stored there, but this fix didn't work for me until I had cleared my drupal caches and local browser caches. In all honesty it took a few clears for me, but that's probably because I did things in the wrong order or missed things.

Thanks,
-Josh Orndorff

stephen Piscura’s picture

Bravo @Andrej Galuf! I've made the changes you mentioned (and undone the hack i previously applied to RedirectResponseSubscriber.php) and everything seems to be working flawlessly! I'll report if i experience otherwise. Thanks again!

afinnarn’s picture

As of Drupal 8.2.4, I now no longer have an issue with Drupal 8 installed in a subdirectory. Previously, with the same Apache setup and no change to the .htaccess file, I did have issues, but they don't happen anymore. Does anyone else still have issues?

JoshOrndorff’s picture

Just did a fresh install of Drupal 8.2.4 on my server (a shared host running CentOS). I had the same issue as before, and the same fix from #30 still worked.

I only experience the issue when .htaccess is setup to redirect to the subdirectory. At first I was happy to just type example.com/subdirectory and had no problems. But once I set up .htaccess and type example.com the issue appears and the fix works.

JoshOrndorff’s picture

Reporting back one more time to point out a problem with the fix from #30.

I'm now getting the drupal access denied page when trying to install a theme or module through the web interfaces at /admin/theme/install and /admin/modules/install .

Removing the patches conceivably fixes the problem, but of course leaves me back with the redirect error.

Rob T’s picture

I am also experiencing the same issue as Josh in #35.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

stephen Piscura’s picture

I've been using @Andrej Galuf's fix in #30 for months with no problems at all. But now i can no longer log in to my site. At mysite.com/user/login the page just refreshes when i enter the username and password, and yet i remain logged out. Is anyone experiencing anything similar? I don't know if it's related to this issue.

stephen Piscura’s picture

Eh, i removed the fix to test without it, and i still can't login. Must be something else. Web development is the worst.

David_Rothstein’s picture

Stephen, a possible tip: The issue you described can sometimes happen due to an incorrect cookie domain (i.e. if the login cookie gets set on a different domain than the one that's in the browser address bar). At least, that's when I've seen it occur in the past. As you said, it is possibly unrelated to this issue.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

karolus’s picture

@Andrej Galuf, I just launched a new site using Drupal 8.3.x, and still had the issue. Without your solution in #30, it would have been very frustrating, indeed.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

pepemty’s picture

@Andrej Galuf I just installed distro Thunder with D8.5, and your snippet doesn't work.

Thing is, I have used it before, even with Thunder, older version of course, and it works like a charm.

Has anybody ran across this...?
Can anybody point me in the right direction...?

Thanks in advance.

Warm regards from sunny México...!

Jose
-----------------
Edit:
This is what I did to fix this issue:

In our public_html folder we create a .htaccess with following content

RewriteRule ^$ web/$1 [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ web/$1 [L]

Change "web" with your correct subdirectories.
------------

Then in the .htaccess in our/web folder (~user/public_html/web) we edited the rewritebase so that it looked like following

# Modify the RewriteBase if you are using Drupal in a subdirectory or in a
# VirtualDocumentRoot and the rewrite rules are not working properly.
# For example if your site is at http://example.com/drupal uncomment and
# modify the following line:
RewriteBase /web

Again, change /web with your right subdirectories.

-------------

Then in our settings.php (~/user/public_html/web/sites/default/settings.php)

we added this code at the bottom

if (isset($GLOBALS['request']) and
'/subdirectory/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

And once again, change /subdirectory/ with your correct path.

pepemty’s picture

New issue:
I'm also experiencing issue #35, but when I ty to update a couple of modules that somehow escaped Composer's gaze.

Did anybody found asolution here...?

Warm regards.

JimBehr’s picture

I followed #46 and it appears to be working now. Thank you!

Some notes:

  • The site files are installed on a shared hosting server, Drupal 8.5.4, in a subdirectory ("public_html/d8t9_2018_07_05").
  • That subdirectory is mapped (to "public_html/d8t9_2018_07_05") as a subdomain so the URL can be referenced like: subdomain2.example.com
  • The lines added to the "public_html/d8t9_2018_07_05/web/sites/default/settings.php" are:
if (isset($GLOBALS['request']) and '/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {  
  $GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}
sunja’s picture

Well the above fixes do work after the site and browser caches are cleared (thanks!) but subDirectory installs really need to be supported officially with well documented install instructions.

unstatu’s picture

#48 also worked for me.

My situations is:
- Drupal is installed in a shared hosting server
- The hosting only allows to point Apache's document root to: /home/myweb/www
- Drupal is installed inside /home/myweb/www with composer. So the index.php file is locate inside /home/myweb/www/web
- In order to redirect the requests to the correct folder, I have the following .htaccess file in /home/myweb/

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} !^/web/
RewriteCond %{HTTP_HOST} ^(www\.)?myweb\.
RewriteRule ^(.*)$ /web/$1 [L]

bas123’s picture

Issue summary: View changes

I am trying to understand the above and assimilate it into what I believe is the same issue but different circumstances:

Drupal 8.5.6

Recently moved from a development Subdomain that pointed to /home/myusername/dev/site/.

There was never a problem with the site running in this version.

However now moved into Production, the site (duplicate with copied database) runs in /home/myusername/public_html/site.

So the URL in the browser is: ‘https://mywebsite.net/site/

So to remove the '/site"I created a .htaccess file and placed it into the ‘home/username/public_html/’ with:

#
# Apache/PHP/Drupal settings:
#
# Redirect to Drupal site (Added 8-10-18).

RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} !^/site/
# Rewrites all URLS [Replace "example" with the actual domain, without the TLD (.com, .net, .biz, etc)]
RewriteCond %{HTTP_HOST} ^(www\.)?mywebsite\.
# Rewrite all those to insert /folder
RewriteRule ^(.*)$ /site/$1 [L]

The settings.php is set as:

  $settings['trusted_host_patterns'] = array(
    '^mywebsite\.net$',
    '^.+\.mywebsite\.net$',
    '^xx\.xx\.xxx\.x$',
  );

When I enter ‘https:mywebsite.net’ into the browser, it now resolves to the /site/index.php (home page) with no issues (meaning that the "/site" is excluded), except from there two things occur.
I lose the authentication I had when the browser is pointed to the ‘site’ directory
And if I try to login from there, the login page renders:
Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.

More detailed error:

Drupal\Core\EventSubscriber\RedirectResponseSubscriber->checkRedirectUrl(Object, 'kernel.response', Object)
call_user_func(Array, Object, 'kernel.response', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.response', Object) (Line: 191)
Symfony\Component\HttpKernel\HttpKernel->filterResponse(Object, Object, 1) (Line: 260)
Symfony\Component\HttpKernel\HttpKernel->handleException(Object, Object, 1) (Line: 79)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 666)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Also, any navigation beyond that in ‘https:mywebsite.net’ seems to resolve back to: ‘https:mywebsite.net/site/*.*

Navigation is hit and miss from the site folder and back to the redirect. Meaning that if I am at:
https://mywebsite.net/site/some_page_alias
My Authentication is in place and the page appears as it should, however, if I manually remove the '/site' so the browser is pointed to:
https://mywebsite.net/some_page_alias
The Authentication dissappears, but the page still opens in the browser just the same.
BUT if I then try to login at:
https://mywebsite/user/login
The error shows up:
Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.

Bottom line, the site will run all day with the '/site' folder shown in the browser, but without it authentication and user functions are lost.

Also, if I am on https://mywebsite.net/any_page_url, the respective page is displayed but when a menu or internal page link is clicked any content link from a node switches to show the 'site/ directory (ie: https://mywebsite.net/site/clicked_page_url), but a "Basic Page" will not necessarily react needing the 'site/' (ie: https://mywebsite.net/clicked_page_url)!!!

The main thing that doesn't make any sense to me is that all of this involves the same base URL, none is an "External" URL as the error would suggest!

I would like to add:

I just logged into the site under:

https://mywebsite.net/site/user/login

as User 302 (Example "Username 4")

and then logged in using the same open browser session under:

https://mywebsite.net/user/login

as User 345 (Example "Username 7")

And the authentication switches between URL's

https://mywebsite.net/site/user/login as "Username 4" and https://mywebsite.net/site/login as "Username 7"..

Clearly this MUST BE FIXED!!!

Any thoughts?

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

cannandev’s picture

#48 worked for me. Don't forget to rebuild your cache once you've made the change.

slawomir’s picture

#46 did it for me. Nice that it doesn't hack the core, and lets me access other directories at the web root level.

auxiliaryjoel’s picture

#46 Did it for me. Drupal 8, cPanel shared hosting.

auxiliaryjoel’s picture

Now that I have this working,
I've noticed that my "git push..." no longer works.
It may be a coincidence, or it may be due to this redirect in place?

This is the git command I run:
git push origin2live master
note: this has worked on all other occasions for months,

I received an error.
At first I thought I may have had changes on server, so I tried to override them:
git push -f origin2live master

the error that I'm getting when trying to git push to my live shared hosting server is:

Counting objects: 13, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (13/13), 1.21 KiB | 1.21 MiB/s, done.
Total 13 (delta 10), reused 0 (delta 0)
To path/to/my/repo/public_html/.git
! [remote rejected] master -> master (Working directory has unstaged changes)
error: failed to push some refs to 'ssh://path/to/my/repo/public_html/.git'

Has anyone else had experiences with this that relate to the changes as per #46?

etron770’s picture

Drupal commerce kickstart composer installation.
Same problem #46 - #48 worked

jmoruzi’s picture

I have a similar set-up to what's described above, installed using composer in ~user/public_html/web

#46 (and #48) mostly work, with one major issue in my case. If the .htaccess in the web directory file has a redirect (such as the www to non-www version of site) set up, the site redirects but inserts /web in the path again so you get a page not found error at example.com/web

This goes back to the original problem of trying to remove '/web' from the path but with a redirect '/web' is inserted into the path again. This happens with all pages on the site, not just the home page. Anyone have any ideas how to solve this?

arsn’s picture

Hello,

I face an equivalent problem which happens in environments where language.negotiation is set to domains (http://langdomain.example.com).

The lang_dropdown switcher redirects to subdomains URLs which are improperly considered as external and lead to the "Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it." error.

The problem doesn't exist on environments where language.negotiation is set to path_prefix (http://www.example.com/langdomain).

[EDIT] In both environments, the trusted host patterns include the subdomains...

The patch works but introduce a security regression according to #2507831: Harden redirect responses to make external URIs opt in (was SA-CORE-2015-002 foward-port) as explained in #2532212 Redirect to external URLs now requires a special object.

Better documentation on proper implementation could be good since subdomains & ports redirecction seem to be considered as external redirections by design.

Thanks

Greenman77’s picture

Like most people, solution #46 seemed to go the distance until you want to redirect to www instead of a non-www address using the usual methods. What worked for me on a cpanel apache style dedicated server was modifying the base project .htaccess in public_html with an extra redirect rule for the www instead of in the drupal root.

In public_html/.htaccess

RewriteBase /
# extra rule
RewriteCond %{HTTP_HOST} !^www\.sitename\.com\.au$ [NC]
RewriteRule .* https://www.sitename.com.au/$1 [L,R=301]

RewriteRule ^$ web/$1 [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ web/$1 [L]

In /web/.htaccess

I make no changes

settings.php

if (isset($GLOBALS['request']) && '/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

NickHopps’s picture

if (isset($GLOBALS['request']) and '/docroot/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
  $GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

worked for me, but i can't install module from UI now, it just shows "access denied", anyone knows how to fix this?

rickharington’s picture

Hi everyone

This has worked for me on two sites I've now built. The first was in subfolder /docroot/ and the second in /web/

Using /web/ as an example below;

In public_html/.htaccess (you have to create this file)

RewriteEngine on
RewriteRule (.*) web/$1 [L]

In /web/.htaccess

No changes

In settings.php

if (isset($GLOBALS['request']) && '/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

Replace {web} with whatever your subfolder is.

Hope it helps someone.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

victor.galguera’s picture

#63 worked for me, thanks!

avpaderno’s picture

Assigned: pueckler » Unassigned
andypost’s picture

sankarprakash’s picture

#46 Worked for me. Thanks @PepeMty

jshimota01’s picture

#46 worked for me. thank the maker. Sucks to spend months developing in a local dev environment only to get hosed on the production server!

ddhuri’s picture

#63 Worked for me. Thanks

freddy rodriguez’s picture

#63 is working fine.

OleCode_’s picture

#63 is working for me on my drupal 9.1.2 (local/production) thnx

futuredesignuk’s picture

#63 & #46 also works for me, excellent support here guys.

  • D9.1.4 installed via composer
  • Hosted on Azure Linux App Service
  • Drupal deployed using Git CI/CD to /site/wwwroot/web/
  • The App Service is fixed to serving from wwwroot - AFAIK cannot change it easily

wwwroot .htaccess

<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteRule ^$ web/$1 [L]
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ web/$1 [L]
</IfModule>

wwwroot/web .htaccess

This works with or without this line below, left commented out or breaks things on local (DDEV)

# RewriteBase /web

settings.php

if (isset($GLOBALS['request']) and
'/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

All good, just a few links like 'Back to site' in admin area top nav go to /web/admin instead of /admin and the ones at the end of completing update.php

geufroi’s picture

my god #63 worked marvelously well for me, thanks !

marioangulo’s picture

Does anyone know how this would work if a symlink is used for the folder. So if I had a subdirectory of web but its a symlink of docroot?

nhcyberguy’s picture

Some background on the experience that i had with this issue. Part of this echos #63's solution:

Shared Hosting Environment
Drupal 9 Hosted in subdirectory (~\public_html\FOLDR\web)
Site was only loading from subdirectory suffix (www.exampl e.com/FOLDR/web)

Here is what I did to get it working on my site, no symlinks:

1. Add an .htaccess file to public_html folder - [cd public_html followed by nano .htaccess]
2. Add the following to the .htaccess file:

RewriteRule ^$ /FOLDR/web/$1 [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /FOLDR/web/$1 [L] 

3. Save it [CTRL+X, Y, Enter]
4. I didn't need to uncomment or edit the RewriteBase rule in the .htaccess file found under "/public_html/FOLDR/web/.htaccess"
5. Edit your settings.php file [cd FOLDR/web/sites/default followed by nano settings.php]
6. Note that your settings file may be write protected, temporarily chmod 640 file so you can edit it, chmod back to 444 when done. Get current numeric permissions level of file via "stat settings.php" command.
6. At the bottom of settings.php add this:

if (isset($GLOBALS['request']) and
'/FOLDR/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

7. Clear Drupal cache
8. Clear web browser cache
9. Use site and test fully (i found some links that had to be adjusted with "/" added for proper resolution)

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

yahyaalhamad’s picture

If you want to solve the problem in a somewhat clean approach, you should define a new EventSubscriber and subscribe to the 'response' event. Make sure it runs before the 'checkRedirectUrl' event_handler.


namespace Drupal\subdomains\EventSubscriber;

use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\subdomains\Entity\Subdomain;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Class SubdomainSubscriber.
 */
class SubdomainsSubscriber implements EventSubscriberInterface {
  /**
   * This will make sure that the subdomains redirects are trusted.
   *
   * @param ResponseEvent $event
   * @return void
   */
  public function subdomainsTrustedRedirect(ResponseEvent $event) {
    $response = $event->getResponse();
    if ($this->isSubdomain($response)) {
      $event->setResponse(TrustedRedirectResponse::createFromRedirectResponse($response));
    }
  }

  protected function isSubdomain($response) {
    // Check if the redirect url is a subdomain.
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    // Run before Drupal's event.
    $events[KernelEvents::RESPONSE] = ['subdomainsTrustedRedirect', 1];
    return $events;
  }
}

avpaderno’s picture

Version: 9.2.x-dev » 9.4.x-dev

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

damienmckenna’s picture

I ran into this specific error via a Webform confirmation page redirect. While debugging the error I found that it was trying to load http://example.commyform/confirmation?token=something instead of http://example.com/myform/confirmation?token=something, so somewhere in the system it's loosing the leading slash on the page path.

It turned out this specific problem was caused by incorrect path_alias records that didn't have a leading slash on the "alias" records. I fixed the bug with this hook_post_update_NAME() script:

function MYMODULE_post_update_fix_prefixless_path_aliases() {
  $storage = \Drupal::entityTypeManager()->getStorage('path_alias');

  // Get a list of aliases that do not start with a slash.
  $query = $storage->getQuery();
  $query->condition('alias', '/%', 'NOT LIKE');

  // Update each alias.
  $aliases = $storage->loadMultiple($query->execute());
  foreach ($aliases as $alias) {
    $alias->setAlias('/' . $alias->getAlias());
    $alias->save();
  }

  return t('Fixed %count path aliases that did not have a leading slash.', [
    '%count' => count($aliases),
  ]);
}

Hope that helps someone else.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

myself.csingh’s picture

#46 worked for me.

lubwn’s picture

In my case I was getting error because Drupal is behind reverse proxy. My problem is that it is a bunch of different servers all pointing to different subdirectories, while I do not have an access to the main server where the main website lives.

So in my case I did not need to "remove" the subdirectory but ADD the subdirectory instead. Which worked, but also resulted in TrustedRedirect errors.

My settings.php file is:

$trim_path = '/subdirectory';
if (isset($GLOBALS['request'])) {
  $request = $GLOBALS['request'];
  if ($request->server->get('SCRIPT_NAME') == "/index.php") {
    $request->server->set('SCRIPT_NAME', '/subdirectory/index.php');
	$request_uri = $trim_path . substr($request->server->get('REQUEST_URI'), 1);
	
	$request->server->set('REQUEST_URI', $request_uri);
  }
}

To fix trustedRedirect errors I used rather stupid jquery front-end fix, but it works:

/* Fix trustedRedirectResponse */
$( document ).ajaxComplete(function (event, xhr, settings) {
	
	$("a").each(function() {
		$(this).attr("href", $(this).attr("href").replace("edit?destination=/node/", "edit?destination=/subdirectory/node/"));
	});
	
});

I had trouble in ajaxified views, which lead to wrong redirects but it was all in GET parameter, so this basicly just replaces href attribute with proper URL.

gzveri’s picture

#76 is the only solution, clean and worked perfectly for installing Drupal10 in a subfolder of root folder /public_html for a primary domain on linux deluxe shared account on godaddy.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

selinav’s picture

#76 works for me with Drupal10 with hostinger.

I have adapted the path :

if (isset($GLOBALS['request']) and
'/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

zuhair_ak’s picture

#76 worked for me

joachim’s picture

Priority: Normal » Major

This is surely a major.

I just moved my blog site to a cPanel-based host, and *NOTHING* worked.

I used the .htaccess redirect from https://www.drupal.org/hosting-support/2021-09-21/unable-to-set-document... to get the document root as /web to work, but then I hit this.

avpaderno’s picture

I am going to restore the issue summary, which was removed from a previous edit.

avpaderno’s picture

Issue summary: View changes
joachim’s picture

The fix in #30 also needs to handle update.php, so in full:

if (isset($GLOBALS['request']) && '/subdirectory/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
  $GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}
elseif (isset($GLOBALS['request']) && '/subdirectory/update.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
  $GLOBALS['request']->server->set('SCRIPT_NAME', '/update.php');
}
bernard63’s picture

#92 is perfect for me
Thank's Joachim

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

ressa’s picture

Title: Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber » How to run Drupal on shared hosting via /web subfolder
Component: routing system » documentation
Category: Bug report » Task
Issue summary: View changes
Status: Needs work » Active

Thanks everyone for sharing your solutions, I have attempted to document the basics on the doc page Relocated web root on shared hosting with other methods.

I have kept the example very basic, but extra notes for specific use cases could of course be added below, so feel free to check it out, and add extra info.

Since this issue has pivoted from being about a local development bug in 2015 into about documenting how to get a Drupal site to work on shared hosting, I am updating the Issue Summary and title (originally "Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber").

I created a new issue with a feature request, since having this option would help a lot of users, and make Drupal easier to run on sharing hosting: #3569379: Add support for setting web root to /web in settings.php. Or the feature could be worked on here ... what do you think @joachim?

joachim’s picture

I don't understand why this has been changed to a task -- yes, people have been sharing how to get it working, but that's because until this bug is fixed, workarounds are needed, and this issue is where people are going to land when they encounter the problem.

ressa’s picture

Title: How to run Drupal on shared hosting via /web subfolder » Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber
Component: documentation » routing system
Category: Task » Bug report
Issue summary: View changes
Status: Active » Needs work

All right, I have changed it back to a bug report. Maybe you can update the Issue Summary to clarify what needs to be done?

Thanks!

mw4ll4c3’s picture

The workaround doesn't seem to be adequate for 11.4.x

Notes and thoughts on the feature request thread: #3569379: Add support for setting web root to /web in settings.php

mw4ll4c3’s picture

I looked into the failure of the workaround in #30, #60, and #76 when using Drupal 11.4.x.

It's tied to the changes to bootstrapping and request handling:

  1. Changing the globals in settings.php is too little, too late, for when $base_url is prepared in the kernel.
  2. The redirect checking calls Drupal\Component\Utility\UrlHelper::externalIsLocal()
  3. externalIsLocal compares the target against $base_url -- which includes the subfolder -- and so it fails.

This is tied to deep changes, so stay on 11.3.x for the time being.

ressa’s picture

Thank you so much for sharing your findings and a possible workaround @mw4ll4c3. I tried the patch locally in DDEV by switching the root from /web to project root of the installation by setting docroot: "" and webserver_type: apache-fpm in .ddev/config.yaml, and adding a .htaccess file with redirect to /web.

The code snippet worked well, and got rid of the error message, and the site worked as expected. I could create content, change user name, and do other regular actions.

About /update.php, it seems to also work. The only detail, is that after accessing and clicking through the steps under /update.php, it seems like /web inserts itself in the path during the process, so you and up at https://example.org/web/ ... It's not a critical issue, more like a detail. But it would be great if other users could also give it a try, and see if we can find other edge cases.

It's the file /core/lib/Drupal/Core/DrupalKernel.php.

ressa’s picture

Title: Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber » .htaccess redirect on shared hosting error: Redirects to external URLs are not allowed by default
Priority: Major » Critical
Issue summary: View changes

If Drupal 11.4 doesn't work on shared hosting where you cannot update Apache or set a DocumentRoot, isn't this a step up from Major, and actually Critical?

Also updating the Issue Summary with steps to reproduce.

apparatchik’s picture

For everyone on a shared hosting or simply using solutions such as cPanel and WHM to manage multiple accounts / drupal sites such as a web agency with multiple clients the 11.4 update brakes Drupal and is indeed critical, I have just alerted a few agencies in such situations to not update to 11.4 for the time being.

Just on said agencies we're talking of tens of sites of all sizes of businesses and institutions.

ressa’s picture

Thanks for the feedback @apparatchik and confirming that this is critical for Drupal installations on cPanel or cPanel-like hosting providers, where .htaccess redirect is the only available method. I am attaching a patch of the code by @mw4ll4c3 from comment #99 purely to allow easier testing in Drupal 11.4, for anyone interested.

It is less than ideal that many Drupal installations are now not able to update Drupal core, especially in light of all the many security updates recently. What if another Drupalgeddon-like (25/25, Highly Critical) security hole is found?

Ideally, this had been fixed a long time ago (this issue was created in 2015) and a test added, verifying that there is a simple method to configure Drupal to use a relocated web root, when it cannot be configured in Apache, or set via DocumentRoot in the web host GUI.

ressa’s picture

Issue summary: View changes

@mw4ll4c3 has shared a more flexible workaround in #3569379: Add support for /web as a subdirectory of the webroot for Drupal 11.4, please test it and share your feedback, thanks.. Update: It is experimental and not ready for use, so only use test locally.

mw4ll4c3’s picture

Please do not use that. It's a proof-of-concept with known limitations and flaws.

This is not a winking disclaimer. That is not for putting in your website to make it work.

The Actual Fix

Stay on Drupal 11.3.x. That's okay for now: support lasts until December!

Look at other options in case a real fix never comes.

If you accidentally upgraded, restore the backup that you made before updating.

Adapted Workaround

No guarantees, but:

Add a new line to your .htaccess:

# New Line
SetEnv DRUPAL_WEBROOT /web
# Existing Lines
RewriteCond %{REQUEST_URI} !^/web/
RewriteRule ^(.*)$ /web/$1 [L,QSA]

Instead of doing anything in settings.php, add the block marked NEW WORKAROUND to /web/autoload_runtime.php:

use Drupal\Core\Runtime\DrupalRuntime;

$webroot = getenv('DRUPAL_WEBROOT');
if ($webroot !== FALSE && str_starts_with($_SERVER['SCRIPT_NAME'], $webroot)) {
  $_SERVER['SCRIPT_NAME'] = substr($webroot !== FALSE && $_SERVER['SCRIPT_NAME'], strlen($webroot));
}

// By default, the symfony/runtime component would load SymfonyRuntime as its
// runtime. However, Drupal's Kernel has a lot of runtime components that it
// expects to be prepared. Thus, we default Drupal applications to DrupalRuntime
// instead to make this easily accessible.
$_ENV['APP_RUNTIME'] ??= $_SERVER['APP_RUNTIME'] ?? DrupalRuntime::class;
return require __DIR__ . '/../vendor/autoload_runtime.php';
ressa’s picture

Issue summary: View changes

Thanks for clarifying, it's great feedback and I updated the Issue Summary, making it clearer that it is experimental, and not to be used in production.

mw4ll4c3’s picture

Issue summary: View changes
mw4ll4c3’s picture

Issue summary: View changes

I cleared out the links to the thing that nobody should use again.

The workaround -- as good of one as there will be -- is in comment #106.

mw4ll4c3’s picture

karolus’s picture

Will defer to your expertise. Is this issue due to updates to Symfony? I may have come across this explanation in my search for causes and solutions.

If I'm understanding correctly, it's SOP to not hack core, correct? Before, redirects could be handled easily by the web root .htaccess or the settings.php for the Drupal instance.

mw4ll4c3’s picture

#106 will adjust the script name for web/index.php and web/update.php.

No guarantees of course.

@karolus:

Will defer to your expertise.

Hi @karolus -- my expertise is pretty thin in this particular case. I needed to read through a lot of Symfony and Drupal code, very quickly. I'm hoping an adult will come to check on us soon, LOL

Is this issue due to updates to Symfony? I may have come across this explanation in my search for causes and solutions.

Close! This isn't about updates to Symfony itself -- the cause is the updates in Drupal 11.4.x to use Symfony more extensively (and Symfony Runtime in particular).

It's worth it, but: Symfony computes its private values for base path, base url, etc, long before settings.php runs. It's impractical to override the function where it ultimately happens, especially as a workaround.

All we can really do is change the $_SERVER values early enough. The target for the workaround is autoload_runtime.php, which is invoked by both index.php and update.php.

If I'm understanding correctly, it's SOP to not hack core, correct? Before, redirects could be handled easily by the web root .htaccess or the settings.php for the Drupal instance.

Yes, but: define standard operating procedure... and tell me if it includes running Drupal with a webroot other than /web, right? ;)

But yes, you're correct: it's a bad practice philosophically, and means you have to re-apply (and possibly rework) the workaround after every update.

In the associated Feature Request, I'm putting stuff together as an MR (and patch), but limit your expectations of anything getting committed.

karolus’s picture

Thanks for clarifying this.

Drupal, or any other CMF, should be able to handle pathing based on standard server requirements. To add another wrinkle—certain deployment tools like Capistrano can have additional pathing (shared—mostly what's in sites/default and releases—the codebase). It's great that there's additional security, but will be compromised if administrators need workarounds to keep sites running.

mw4ll4c3’s picture

It's great that there's additional security, but will be compromised if administrators need workarounds to keep sites running.

I'm pretty confident that security isn't actively compromised by #106.

Altering SCRIPT_NAME is not "best practice" -- but that's because "best practice" is not being in a situation that requires it. Patching core isn't "best practice" but that's how we can alter SCRIPT_NAME in time.

Doing that is at the discretion of you and/or your clients.

In Symfony

I won't pretend to know everywhere SCRIPT_NAME and the variables derived from it are used by Symfony, or if any of those are "funky" -- but I doubt it.

SCRIPT_NAME

I know the opposite is true: modifying SCRIPT_NAME to make it match what the client sees, is important for security features to work.

Stuff like trusted hosts, proxies, firewalls, and... drumroll... validating redirects! That's the thing that broke and caused the error message. But skipping it entirely is why those awful patches (#23, #24, #25) are awful.

The original workaround makes the browser and server agree, so that those things work. This one does the same thing, the same way. But it has to do it earlier, before Symfony does its thing.

Environment Variables

They can be visible if you have other problems, so it's not wise to put actual secrets in there. Knowing what's in this one isn't dangerous unless you have way bigger problems.

Summary

Changing SCRIPT_NAME, to ensure things are configured correctly? By storing a small part of it in an environment variable? It's fine. Just make sure your file permissions outside of /web are solid, and you (should be) covered. You can "harden it" by hard-coding the value, I guess.

I've done a lot of conflating and hand-waving, some intentional, but it's close enough to get the idea across.

But yeah: the first step should still be "can I set this up the way Drupal expects" and if the answer is yes, do that instead.

karolus’s picture

Thanks for the detailed explanation.

Per the .htaccess that ships with Drupal in the /web folder, this part may need changing as well, then:

  # Modify the RewriteBase if you are using Drupal in a subdirectory or in a
  # VirtualDocumentRoot and the rewrite rules are not working properly.
  # For example if your site is at http://example.com/drupal uncomment and
  # modify the following line:
  # RewriteBase /drupal
  #
  # If your site is running in a VirtualDocumentRoot at http://example.com/,
  # uncomment the following line:
  # RewriteBase /
mw4ll4c3’s picture

Another good question gets another good answer:

Those are for when Apache needs to be aware of hijinks -- not Drupal.

Per this comment:

# For example if your site is at http://example.com/drupal uncomment and
  # modify the following line:
  # RewriteBase /drupal

RewriteBase is for keeping things lined up when you want the subdirectory to appear in the client-side URL. This workaround, and the previous one, are for when you wish it didn't.

The VirtualDocumentRoot case is for when you don't need this workaround, so let's not talk about it :)

Both the workarounds here use .htaccess to internally rewrite / to /web. Apache handles that, but Drupal has no idea it happened. So, we adjust SCRIPT_NAME to make Drupal and the client agree about the base url and path.

karolus’s picture

Thanks for that.

This has me thinking—what about creating a patch for autoload_runtime.php and loading it via cweagans/composer-patches? That way, when a new release for Drupal comes out, running the updates will also reapply the patch?

mw4ll4c3’s picture

Making a patch and posting it here would be handy.

I think we've got this issue back to where it was, for now.

Feel free to come on over and join the Feature Request, which will eventually be more thorough. Right now it's mostly lots of reading front controller code paths and hoping someone who already knows them shows up: #3569379: Add support for /web as a subdirectory of the webroot

siramsay’s picture

Thank you for your hard work on this. I patched a few modules to use TrustedRedirectResponse which worked, but then came across any Save or Submit button in core gave the same error.

My set-up is a Droplet using Apache.

My directory structure is.
/var/www/MYDOMAIN.com/public_html/SUB/web

I have previously needed rewrites in the public_html .htaccess and the setting.php script from here https://www.drupal.org/project/drupal/issues/2612160#comment-11767977

I have now added this to the .htaccess in the Drupal Root, the one in the same directory as the /web/autoload_runtime.php

# Various rewrite rules.
<IfModule mod_rewrite.c>
  RewriteEngine on

  # New Line
  SetEnv DRUPAL_WEBROOT   /SUB/web

  # Set "protossl" to "s" if we were accessed via https://.  This is used later

And have patch the /web/autoload_runtime.php as #106 suggests

This works for onetwo sites so far.

mw4ll4c3’s picture

Awesome! Glad we got you off an unsafe fix.

Did you make sure to roll back those changes to your modules?

The comment with the original version of the workaround says it best:

There's a good reason why all user input data is handled by LocalRedirectResponse. Replacing it with a TrustedRedirect response is like blowing up the front door to ensure the burglar won't break through them.

siramsay’s picture

Thanks for the heads-up on the TrustedRedirectResponse.

I will roll them back as I don't think it is needed now as internal paths are working.

mw4ll4c3 changed the visibility of the branch main to hidden.

mw4ll4c3’s picture

StatusFileSize
new818 bytes

Adding a .patch file against 11.4.x which applies the workaround to autoload_runtime.php.

The suggestion in #117 re: cweagans/composer-patches seems wise, since it will need to be applied regularly. I'll check it out and probably write up some instructions.

b0gucki3’s picture

Thank you all.
mw4ll4c3, here's an alternative patch based on the SCRIPT_NAME as per from #117.
This did not work on some providers with shared-hosting environment I tested.
The previous patch depends on DRUPAL_WEBROOT, but some hosting providers do not expose the .htaccess SetEnv value to PHP (that's ok).

My latest version works out the relocated webroot automatically by comparing DOCUMENT_ROOT with Drupal’s web directory. It also handles symlinks and only adjusts SCRIPT_NAME when the detected path is actually present.

Tested on Drupal 11.4.4 with authenticated redirects and form submissions.

"patches": {
"drupal/core-composer-scaffold": {
"2612160": "https://www.drupal.org/files/issues/2026-07-27/2612160-derived-webroot-c..."
}
}

mw4ll4c3’s picture

Thanks!

I actually didn't know about the scaffolding. Not a thing I've ever looked at/into.

Makes me think I should update my posts about doing this manually, to have people change it in index.php and update.php again, to keep things simpler.