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
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
- Install a fresh
drupal/recommended-projectproject in DDEV with relocated web root, and get everything up and running - Update DDEV to serve from the project root of the installation by updating
.ddev/config.yamlto usedocroot: ""andwebserver_type: apache-fpm - Add a .htaccess file with redirect to /web per Redirect with .htaccess file
-
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
- Login via
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
| Comment | File | Size | Author |
|---|---|---|---|
| #124 | 2612160-derived-webroot-core-composer-scaffold.patch | 1.02 KB | b0gucki3 |
| #123 | 2612160-webroot-workaround.patch | 818 bytes | mw4ll4c3 |
Issue fork drupal-2612160
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:
Comments
Comment #2
tim.plunkettCan you explain the "http://localhost" part more?
Is there a ?destination= in the URL at any point during article creation?
Comment #3
miniwebs2 commentedThe 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.
Comment #4
tim.plunkettIf 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.
Comment #5
miniwebs2 commented2nd 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.
Comment #6
tim.plunkettSo 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.
Comment #7
miniwebs2 commentedOkay - 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.
Comment #8
tim.plunkettWell 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.
Comment #9
miniwebs2 commentedAppreciate 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.
Comment #10
dawehnerSo 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.
Comment #11
miniwebs2 commentedUpdate:
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.
Comment #12
miniwebs2 commentedHave 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.
Comment #13
miniwebs2 commentedDrupal 8.0.0
Local install still creates the issue message.
Remote install works fine.
Comment #14
tim.plunkettSeems like an environment specific bug. Not sure what the exact factor is.
Comment #15
Max Scott commentedI 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?
Comment #16
Max Scott commentedThe 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:
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
LocalRedirectResponsewithTrustedRedirectResponseas the following patch shows.So far this has solved the issue, but I am not sure whether or not this might have security implications.
Comment #17
Max Scott commentedThe 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:
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
LocalRedirectResponsetoTrustedRedirectResponseas the following patch indicates.This seems to have solved the issue, but I do wonder whether or not this change might have security implications.
Comment #18
gábor hojtsy#2643466: "Redirects to external URLs are not allowed by default" for domain-based URL language detections may be related.
Comment #19
shotsy247 commentedI 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:
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
Comment #21
stephen Piscura commentedI 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.
Comment #22
vayu commentedI 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).
Comment #23
justinlevi commentedHere's an actual patch file for #17 above so I can include this in my composer.json file.
Comment #24
justinlevi commentedsorry, adding again as a .patch file.
Comment #25
dave.mikolaj commentedHi 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.
Comment #26
brianV commentedMoving to CNR due to the patch.
Comment #30
andrej galuf commentedThis 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.
Comment #31
JoshOrndorff commented@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
Comment #32
stephen Piscura commentedBravo @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!
Comment #33
afinnarn commentedAs 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?
Comment #34
JoshOrndorff commentedJust 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.
Comment #35
JoshOrndorff commentedReporting 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.
Comment #36
Rob T commentedI am also experiencing the same issue as Josh in #35.
Comment #38
stephen Piscura commentedI'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.
Comment #39
stephen Piscura commentedEh, i removed the fix to test without it, and i still can't login. Must be something else. Web development is the worst.
Comment #40
David_Rothstein commentedStephen, 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.
Comment #44
karolus commented@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.
Comment #46
pepemty commented@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.
Comment #47
pepemty commentedNew 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.
Comment #48
JimBehr commentedI followed #46 and it appears to be working now. Thank you!
Some notes:
Comment #49
sunja commentedWell 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.
Comment #50
unstatu commented#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]
Comment #51
bas123 commentedI 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:
The settings.php is set as:
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:
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?
Comment #53
cannandev commented#48 worked for me. Don't forget to rebuild your cache once you've made the change.
Comment #54
slawomir commented#46 did it for me. Nice that it doesn't hack the core, and lets me access other directories at the web root level.
Comment #55
auxiliaryjoel commented#46 Did it for me. Drupal 8, cPanel shared hosting.
Comment #56
auxiliaryjoel commentedNow 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?
Comment #57
etron770 commentedDrupal commerce kickstart composer installation.
Same problem #46 - #48 worked
Comment #58
jmoruziI 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?
Comment #59
arsn commentedHello,
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
Comment #60
Greenman77 commentedLike 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');
}
Comment #62
NickHopps commentedworked for me, but i can't install module from UI now, it just shows "access denied", anyone knows how to fix this?
Comment #63
rickharington commentedHi 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.
Comment #65
victor.galguera commented#63 worked for me, thanks!
Comment #66
avpadernoComment #67
andypostComment #68
sankarprakash commented#46 Worked for me. Thanks @PepeMty
Comment #69
jshimota01 commented#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!
Comment #70
ddhuri commented#63 Worked for me. Thanks
Comment #71
freddy rodriguez#63 is working fine.
Comment #72
OleCode_ commented#63 is working for me on my drupal 9.1.2 (local/production) thnx
Comment #73
futuredesignuk commented#63 & #46 also works for me, excellent support here guys.
wwwroot .htaccess
wwwroot/web .htaccess
This works with or without this line below, left commented out or breaks things on local (DDEV)
# RewriteBase /websettings.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
Comment #74
geufroi commentedmy god #63 worked marvelously well for me, thanks !
Comment #75
marioangulo commentedDoes 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?
Comment #76
nhcyberguy commentedSome 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:
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:
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)
Comment #78
yahyaalhamadIf 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.
Comment #79
avpadernoComment #81
damienmckennaI 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:
Hope that helps someone else.
Comment #83
myself.csingh commented#46 worked for me.
Comment #84
lubwn commentedIn 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:
To fix trustedRedirect errors I used rather stupid jquery front-end fix, but it works:
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.
Comment #85
gzveri commented#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.
Comment #87
selinav commented#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');
}
Comment #88
zuhair_ak#76 worked for me
Comment #89
joachim commentedThis 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.
Comment #90
avpadernoI am going to restore the issue summary, which was removed from a previous edit.
Comment #91
avpadernoComment #92
joachim commentedThe fix in #30 also needs to handle update.php, so in full:
Comment #93
bernard63 commented#92 is perfect for me
Thank's Joachim
Comment #95
ressaThanks 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?
Comment #96
joachim commentedI 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.
Comment #97
ressaAll 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!
Comment #98
mw4ll4c3 commentedThe 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
Comment #99
mw4ll4c3 commentedI 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:
This is tied to deep changes, so stay on 11.3.x for the time being.
Comment #100
ressaThank you so much for sharing your findings and a possible workaround @mw4ll4c3. I tried the patch locally in DDEV by switching the root from
/webto project root of the installation by settingdocroot: ""andwebserver_type: apache-fpmin.ddev/config.yaml, and adding a.htaccessfile 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/webinserts 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.Comment #101
ressaIf 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.
Comment #102
cilefen commentedComment #103
apparatchik commentedFor 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.
Comment #104
ressaThanks 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.
Comment #105
ressa@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.Comment #106
mw4ll4c3 commentedPlease 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:Instead of doing anything in
settings.php, add the block markedNEW WORKAROUNDto/web/autoload_runtime.php:Comment #107
ressaThanks 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.
Comment #108
mw4ll4c3 commentedComment #109
mw4ll4c3 commentedI 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.
Comment #110
mw4ll4c3 commentedComment #111
karolus commentedWill 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.
Comment #112
mw4ll4c3 commented#106 will adjust the script name for
web/index.phpandweb/update.php.No guarantees of course.
@karolus:
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
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.phpruns. 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.phpandupdate.php.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.
Comment #113
karolus commentedThanks 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.
Comment #114
mw4ll4c3 commentedI'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.
Comment #115
karolus commentedThanks for the detailed explanation.
Per the .htaccess that ships with Drupal in the /web folder, this part may need changing as well, then:
Comment #116
mw4ll4c3 commentedAnother good question gets another good answer:
Those are for when Apache needs to be aware of hijinks -- not Drupal.
Per this comment:
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.Comment #117
karolus commentedThanks 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?
Comment #118
mw4ll4c3 commentedMaking 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
Comment #119
siramsay commentedThank 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
And have patch the /web/autoload_runtime.php as #106 suggests
This works for
onetwo sitesso far.Comment #120
mw4ll4c3 commentedAwesome! 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:
Comment #121
siramsay commentedThanks 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.
Comment #123
mw4ll4c3 commentedAdding a .patch file against 11.4.x which applies the workaround to
autoload_runtime.php.The suggestion in #117 re:
cweagans/composer-patchesseems wise, since it will need to be applied regularly. I'll check it out and probably write up some instructions.Comment #124
b0gucki3 commentedThank 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..."
}
}
Comment #125
mw4ll4c3 commentedThanks!
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.phpandupdate.phpagain, to keep things simpler.