There should be a way to declare the path between the webroot and the /web directory, if any exists, so $base_url, $base_path, and the Symfony Runtime equivalents can be corrected easily.
This is important for both shared hosting environments and complicated domain configurations.
Looking for support?
This thread is for active work toward a portable fix.
It is code that you should not use.
- The original bug report and general discussion: #2612160: .htaccess redirect on shared hosting error: Redirects to external URLs are not allowed by default
- Relocated Web Root on Shared Hosting doc page (for 11.3.x and below)
- 11.4.x-compatible workaround: comment #106
Problem/Motivation
Some environments and setups prevent the webroot from being set directly to Drupal's /web folder, where index.php is. When trying to use .htaccess at the webroot, eg:
RewriteCond %{REQUEST_URI} !^/web/
RewriteRule ^(.*)$ /web/$1 [L,QSA]
This causes an infamous failure:
Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.
The known workaround no longer works in Drupal 11.4.x for reasons listed below.
Another workaround was proposed, using similar methods. However, it requires editing Drupal core files, making it less robust, and more difficult to maintain.
Steps to reproduce
- Have Drupal 11.x Running
- Have a webroot other than Drupal's own "/web"
- Have .htaccess sending requests into "/web"
- Submit any form which has a built-in redirect or destination parameter
Proposed resolutions
Add support for an environment variable, to specify directories between the webroot and /web.
This variable will then be used to remove that path segment from the base path and base url.
Adjust $base_pathdone, inadequateInfluence the output of $request->getBasePath()impractical- Act directly on
$_SESSIONbefore symfony builds the request
Remaining tasks
Test working limits ofinadequate$base_urltweak for in-place sitesSearch for possible opportunities to safely alter the request.impracticalPrepare replacement workaround for bug report threadTestSCRIPT_NAMEmethod for in-place sites- See if
install.phpcan function without SCRIPT_NAME adjustment (is this necessary?) See if(deprecated)authorize.phpcan function without SCRIPT_NAME adjustment- See if
rebuild.phpcan function without SCRIPT_NAME adjustment - Test any fixes with multisite / multilingual setups
- Get feedback from core maintainers.
| Comment | File | Size | Author |
|---|---|---|---|
| #51 | 3569379-support_subdirectory_scaffold.patch | 715 bytes | karolus |
| #50 | support_subdirectory_scaffold.patch | 715 bytes | karolus |
Issue fork drupal-3569379
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
ressaComment #3
mw4ll4c3 commentedUnfortunately 11.4 seems to have made this a bigger problem again. The workaround in #92 is no longer adequate.
Workaround Failure
The error appears again (but not by merely visiting the site):
Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.It can be reliably triggered by (so far):
Brief tests
Manually appending a destination parameter results in the following:
On that second point:
A user, logged in via mydomain.com/user, is not recognized as logged in by mydomain.com/web/whatever, unless they have also logged in at mydomain.com/web/user.
That said: blocking people from seeing a site configured this way by visiting "mydomain.com/web" should also be a goal. So if possible, the instructions for .htaccess in "Relocated web root on shared hosting" should be updated (if possible) to prevent that, for many reasons.
Next steps?
Hopefully this info is useful to someone with more insight to the changes between 11.3 and 11.4 -- that is not me, at least not today.
In any event -- whether or not a fix, workaround, or feature emerges -- the instructions for "Relocated web root on shared hosting" are obsolete. They'll need to be updated, refuted, or slapped with a disclaimer for >11.4
Thoughts on a built-in solution
#2 and #3 together would allow people to get up and running without immediate problems / interruptions. I think a more reasonable scope is "to allow the site to serve from a root containing vendor and web" rather than to attempt "to allow people to specify a custom web root" and all that implies.
Comment #4
ressaWas this comment meant for another issue? I see "The workaround in #92 is no longer adequate." and other references, which are not present here ...
Comment #5
mw4ll4c3 commentedMy apologies for any confusion!
The two things I refer to are #92 in the issue you linked and (by extension) the documentation page that you worked on, and also linked.
I put the details here, since the inadequacy of the workaround makes a formal, permanent solution more necessary... and also to make sure that anyone who tries to tackle this, is aware that the workaround isn't adequate.
I posted a short comment in that issue, linking here, so that anyone who finds the workaround is aware it's not adequate for 11.4.x
Again: apologies. Please give my comment in this thread another read, in this context.
What we probably actually need, is for someone more familiar with the specific differences between <11.3.x, and 11.4.x, to chime in -- I for one don't know where (in the code) to start looking, to try to put together an updated workaround.
Comment #6
ressaNo problem at all and thanks for clarifying!
It all makes more sense now, after I read your comment here, and also checked what the other issue is about.
But ... it seems to me that there are two components here, 1. The error and 2. The proposed support for setting root to /web via the GUI. So in that sense, the first two sections of your comment ("The issue is back, but in a limited way" and "Brief tests with a manually-added destination parameter") might belong in the other issue comment (the bug), since this issue is a Feature request?
About the doc page https://www.drupal.org/docs/getting-started/system-requirements/relocate... feel free to update it, and add a caveat that it might not work in Drupal 11.4.
Comment #7
longwaveI don't think this should be a setting in the UI. What if you set it to an invalid value, how do you get back to the site to fix it? And if it's wrong in the first place how do you get to the page to fix it anyway?
Comment #8
ressaThanks for feedback @longwave, I obviously didn't think this through and you're correct :)
Setting it via the GUI won't really work, since either 1. It cannot find the installation, or 2. The installation is found, but will disappear after an update.
But how about adding support for defining the web root in the
settings.phpfile with something like this?$settings['web_root'] = '/web';Because the current workaround (see #2612160: Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber) seems to no longer work, so having a simple and officially supported method would be ideal.
Comment #9
mw4ll4c3 commentedThe failure of the workaround is tied to the changes in D11.4 to bootstrapping and request handling. Changing the globals in settings.php is too little, too late, for when $base_url is prepared in the kernel.
The check in Drupal\Component\Utility\UrlHelper::externalIsLocal() uses $base_url -- which includes the subfolder -- and fails.
A start is patching DrupalKernel itself, to remove the web folder from the path while building $base_url. I believe they're only used when building links, so I think it should be safe.
I have not tested on a multisite installation of any kind.
In the below, your value would be
$subfolderComment #10
ressaThanks for sharing @mw4ll4c3, great findings!
If
settings.phpis invoked too late in the process, then trying to change it there won't work ... so let's close this issue.I do think you should share your suggestion in #2612160: Redirects being built as http://localhost:8888/drupal8 and not considered safe by RedirectResponseSubscriber rather than in this issue, since it is a feature request, and also with few subscribers. So getting the problem solved (or worked around) is more likely to happen in that other issue.
Also, it would be great if you shared the error message from the logs, how the screen looks (WSOD?), etc. in that other issue, so that any users experiencing the same problem finds it. Or is it the same as originally reported, "Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it."?
Also, you should still feel free to review the https://www.drupal.org/docs/getting-started/system-requirements/relocate... doc page. Both to check which of the workarounds work, and add a caveat about Drupal 11.4 where it's relevant.
Comment #12
mw4ll4c3 commentedYep, that's the one. I'll crosspost it in there later, to up the odds of anyone finding it.
I was hesitant to update the doc page, without double-checking... but the end goal really is just doing the same thing (making $base_url and the request object happy) but with different timing, more flexibility, and less mystery. If the original were harmful, hopefully someone would have spoken up in the "bug thread" (as they did for the dangerous patch).
For now: thank you for helping keep knowledge of this problem out there, and documented!
Comment #13
ressaThanks for posting a thorough comment in the other issue @mw4ll4c3! The relevant users are more likely to see it over there.
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.It 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.
I did run into an oddity: After accessing and clicking through the steps underI read your comment in the other issue more closely, and all is well :)/update.php, it seems like/webinserts itself in the path during the process ...But feel very free to add a caveat in the doc page, I guess under "Redirect with .htaccess file" that to prevent the dreaded "Redirects to external URLs are not allowed by default ..."-error, that patching
/core/lib/Drupal/Core/DrupalKernel.phpis a possible, but experimental solution.Comment #14
mw4ll4c3 commentedOpening this back up to work on a possible formal solution.
This is a drop-in snippet I was been using for a while, with "success".
It's got its limitations, obviously... it only handles $base_url which is on its way out.
But it was handy: the most common case is covered entirely by one extra line in the .htaccess:
...a pattern which could adapt to complex setups.
Comment #15
mw4ll4c3 commentedComment #16
mw4ll4c3 commentedComment #18
mw4ll4c3 commentedComment #19
mw4ll4c3 commentedComment #20
mw4ll4c3 commentedComment #21
mw4ll4c3 commentedComment #22
ressaThanks @mw4ll4c3! That's a flexible solution, since the user can set the value of the web root in .htaccess, and it works great.
Your solution could be shared in the other original issue for more user interaction, since this issue actually sort of happened by accident: In the same moment (comment #95 24 January 2026) I erroneously changed it from a bug report to a documentation task, and created this issue as a feature request, but were corrected by @joachim. So moving the MR and parts of the Issue Summary from this issue to the original issue could be considered, since many more users will see it and can give feedback. On the other hand, I also understand why you don't want to present it as a Drupal core merge request ...
Either way, it's great to see progress in solving this, and I am adding the required .htaccess to match the MR in the Issue Summary, and also attach a Drupal 11.4 patch, since the current one against "Main" is for Drupal 12, where things changed.
Comment #23
mw4ll4c3 commentedThank you for the typos and the clean-up.
Please don't promote stuff that explicitly says "do not use" as a fix. I shouldn't have put it in the "bug report" issue to begin with.
I want to actively prevent people from using that. That code is not a fix. It breaks things because
$base_pathand$base_urlaren't enough.I know this, but a lot of people aren't going to know the difference. Ten years ago, people were applying a security hole. I don't want anyone running a proof-of-concept with known (and serious) flaws and omissions.
I've put together something more equivalent to the "settings.php" part of the old workaround. It's way safer and easier for people to drop in, if anyone really must. It's in #2612160: .htaccess redirect on shared hosting error: Redirects to external URLs are not allowed by default -- comment #106.
After late-night reading in core and Symfony, I still have more to read.
I only ask you be as patient, as you are dedicated and eager :)
Comment #24
mw4ll4c3 commentedAfter some more reading:
Changing
$_SERVER['SCRIPT_NAME']is how it happens (if at all).The way Symfony itself is structured and written* it's simply not practical, from within Drupal, to change what happens when the $_SERVER variables are used to create what gets returned by $request->getBaseUrl (et al)
I'll put up another MR later which does so in the best-suited place.
*hey, at least it was educational
Comment #25
yousefanbar commentedHas anyone solved this issue?
Comment #26
ressa@mw4ll4c3: Thanks for the feedback, and I agree that it's best to make it very clear that the changes are still Proof of Concept and experimental, and not to be used in production. So adding caveats are necessary, since this changes a fundamental part of Symfony, and thereby Drupal. If it was Drupal 11-ready, we should add https://www.drupal.org/project/bad_judgement as a requirement in the patch :)
I really appreciate that you took the time to study Symfony and found a better place to add relocated
/web-support, and thanks for adding it to the Relocated web root on shared hosting doc page.I hope more Drupal core developers will join you in your efforts, so you can all collectively find a robust and resilient method for Drupal core, to add support of the officially recommended relocated web root, for those Drupal installations on shared hosting who cannot alter Apache configuration, or set documentRoot via GUI.
@yousefanbar: Not yet, so feel free to share any ideas for a solution.
Comment #27
mw4ll4c3 commented@yousefanbar:
Hello! Please don't use patch 22 or MR !16356.
There's a workaround in this comment that should do the trick.
I have updated code to put in this thread later, but: it's the same code, only where it "belongs" instead of where it's easy for people to do.
@ressa:
I made a "user friendly" workaround which is actually intended to work. That's the thing that people should get linked to.
Please stop telling people to use MR !16356 and the patch from it. I removed it everywhere I could for a reason.
It was part of a thought process. It's not an "experimental" fix and it is definitely not part of "Steps to Reproduce." That code won't help anyone. Feedback on it won't help me. It was used it to confirm a few suspicions, and that's it.
I am not saying "it needs a disclaimer." I am asking you to please stop making links to it.
I will hopefully be back with an actual MR soon.
Use the workaround in comment 106.
Comment #28
mw4ll4c3 commentedComment #33
ressaAll right, my intention was only to help the process gain traction, and I assumed (wrongly, I now see) that it was only a matter of time before the missing .htaccess bit was added here in the Issue Summary, so that the MR + .htaccess would be the same as the "user friendly" workaround.
So I'll step back, and not get in your way, so you can focus on the task :)
Comment #38
mw4ll4c3 commentedUnderstood and appreciated, honestly. I just needed your patience to match your determination and enthusiasm :)
This last MR and the #106 workaround ended being similar, but:
the "workaround" version is to do it in a small file in
/web.the MR is in the file it belongs in, down in
/core/lib/Drupal/Core/Runtime...so even if I did "the real one" first: different audience, for a different usage = different package
The workaround is #106, where it's easier to find / remember, and the file itself is less likely to change. That's important to me, since (if no fix gets committed) this is a workaround that needs to be reapplied every update.
That's important for smaller / less experienced shops -- I bet there's a lot of overlap between those, and people who need this patch!
Comment #39
mw4ll4c3 commentedGood news: MR !16369 passed its tests. Probably won't get committed, but -- you never know unless you ask ;)
The workaround version uses the same mechanism in (almost) the same place in the code flow, so -- if the workaround works, this will work.
A big help would be, if you can test it in a safe place, including install.php and update.php. Be sure the ".htaccess stuff" is in place before you try to install.
Comment #40
mw4ll4c3 commentedThe change in
DrupalRuntime's constructor won't coverinstall.php,authorize.php, andrebuild.php.At a glance, the three don't have a lot in common, so I'm going to put this back down for a bit, before double-checking.
Not ideal, but they can get spot-fixed, if needed. If that's the case, I'd try to find an appropriate place to put a function which they can all make use of --- which might just end up being an include. We'll see.
This means for the workaround covers the two big ones fine -- which the old one was (out of the box) limited to, actually -- but it's simply not there yet. So, it's enough for a rescue, but not enough to declare support (which the environment variable will imply).
It'll be a bit before I can get back on this.
Comment #41
ressaI'll try to constrain myself, but it's not easy :)
The workaround with the MR 16369 here and the .htaccess shared in comment #106 in the main issue and the Relocated web root on shared hosting doc page work well for the common use cases like accessing /admin/reports/updates/check, flushing caches, etc. I added a link to main issue in the "Relocated web root ..." doc page, so that users know where to get the latest about a permanent solution for this bug.
You caught that it won't cover install.php, authorize.php, and rebuild.php before I got around to try that out. It's great that you may be able to come up with a solution for those as well later, but it's really useful that the current workaround covers the most important parts.
Thanks for your great efforts so far, and your latest thorough comment in the main issue about the current status. It's great that you got the ball rolling, so that hopefully more Drupal core developers will join in and find a solution to this. If not, like you wrote in the main issue, users should prepare themselves to look at other options in case a real fix never comes, like hosting where the user can easily set DocumentRoot to /web via the settings panel.
Comment #42
mw4ll4c3 commentedThank you, almost-only other proactive person :)
I tweaked the doc page a little bit for clarity and to be more user friendly.
Thank you for testing!
It's good timing to post an actual patch file of "the workaround version" (changes
autoload_runtime.php) over there in the bug report, and then add a quick little link to it on the doc page. Want to tackle that while I keep digging?For the record, neither did the old workaround.
But I don't know if they need to be covered. I think we might be fine!
But again, I haven't REALLY looked through them.
For the workaround, for now, we just need to make sure that 11.4 made us have to ONLY change the means (
settings.php). That the method (modifyingSCRIPT_NAMEearly) doesn't break anything that involves/web/index.phpand/web/update.php.On what IS covered:
The old workaround explicitly targets only
/web/index.phpand/web/update.php. The "omission" of the others isn't new, and......which are probably the only parts most people using it "need."
Comment #43
mw4ll4c3 commentedComment #44
ressaYes, it's great to see the activity in the other issue as well!
Thanks for clarifying about the three paths, and that they may not be so relevant after all, since the old workaround did the job seemingly well enough, also without handling those three paths. And great that you are looking into them still, even if they are not crucial, or maybe even seemingly deprecated, like Authorize. I am about to call it a day, so maybe you can handle the patch?
To be prepared, I am looking into hosting alternatives with easy DocumentRoot settings, to add them to https://www.drupal.org/docs/getting-started/system-requirements/hosting-.... So far I found Hosting and Scalahosting, but popular hosts such as bluehost, SiteGround, and Hostinger seem to not support changing DocumentRoot easily. I thought the community could maybe share any good and affordable hosts they know of, who offers that in an issue, so I created #3612376: Shared hosts for limited budget projects with Composer and DocumentRoot support.
Comment #45
mw4ll4c3 commentedYeah, I can confirm: Hostinger's lower-tier plans definitely don't allow it. Their shell is also currently PHP 8.2 regardless of which version your server actually runs (but you can run composer and drush just fine -- for now? -- by calling them through the proper version's executable).
In my experience, a lot of people hear about them being "good cheap Drupal hosting," get a plan that isn't VPS, and start hitting roadblocks immediately with anything but the auto-install "set up Drupal on my domain" button.
Comment #46
ressaThanks for confirming, and I have also run into the CLI PHP version not being synced with the actual installation PHP version on other hosting platforms, where I had set it in the root .htaccess file, or prepending commands with the PHP version. It's a mess. Ideally we can collect a handful of web hosts which support Composer via SSH, setting DocumentRoot, and automatically synced CLI/installation PHP version (8.3, 8.4, or 8.5).
Ok, that was a slight detour, now back to support for /web subdirectory :)
Comment #47
mw4ll4c3 commentedSo, rebuild is fine, until the redirect at the end. Do we care? I'd rather it be fixed.
But: the heat is off --- the workaround's good! -- so I'm not sure when that will be.
Covering everything means reintroducing
core/includes/request.inc(pretty cheeky) to hold the function, and calling it from each of the front controllers (index.phpet al)The "workaround" used
autoload_runtime.phpto simply things -- one paste instead of two -- but that's not where it belongs. My last attempt was a nice try, but it would make a mess if I kept it. It's better to just use the same pattern in all of the front controllers, and they'd all be within two directories ofrequest.incso it's clean and clear.Next one from me will be the last one, when it comes -- I made a couple of sloppy mistakes trying to do it between other things, so I'm going to wait until I've bought time (or the weekend comes). But it will be good for
Thank you for testing and documenting!
Comment #48
ressaI appreciate how you aim for the simplest method, and look at all options, since these are delicate files to change, and not everyone is super comfortable editing Drupal core files.
But however the workaround will look in the end, so long as users can do basic administrative tasks without getting the "Redirects" not allowed error with this workaround, it's a big improvement :)
Comment #49
karolus commented@mw4ll4c3: I see that patching has been deprecated, but nonetheless was able to create one from comment #106 from this issue, as @b0gucki3 had done.
Also, in thinking this problem over, it reminds me of a similar issue with Drush linking. To get around that, the recommended fix is to add symlinks that can be called on. The only problem I would see with symlinking /web is when having separate environments on one server. That's currently what I deal with, say having a staging.site.tld and site.tld.
Comment #50
karolus commentedComment #51
karolus commented