Hello,

I'm converting a large existing website to run on Drupal. Part of the website runs on complex, custom-written php. Converting it to a Drupal module is something I don't have the skills or time to do right now, but it runs perfectly well and doesn't actually need to be moved into Drupal anyway. My plan is just to theme it the same as the rest of the site and otherwise let it run in parallel. Drupal will run in the site's root directory, as you'd expect, so this custom php will need to run in a sub-directory.

When I try to run the script, I just get a 403 error.

I can't find anything online specific to D8, but there's plenty of information about this problem for D7 and earlier. As a result, I've tried a few things, but whatever I try I just get a 403 again.

I have chmod'ed the custom .php script (index.php) and the subdirectory containing it and both are now 755 (though I also tried 777). This didn't make any difference.

In the .htaccess file I made this change (where my_custom_subfolder is the path to the php script):

  # For security reasons, deny access to other PHP files on public sites.
  # Note: The following URI conditions are not anchored at the start (^),
  # because Drupal may be located in a subdirectory. To further improve
  # security, you can replace '!/' with '!^/'.
  # Allow access to PHP files in /core (like authorize.php or install.php):
  RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
  # Allow access to test-specific PHP files:
  RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php
  # Allow access to Statistics module's custom front controller.
  # Copy and adapt this rule to directly execute PHP files in contributed or
  # custom modules or to run another PHP application in the same directory.
  RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
  RewriteCond %{REQUEST_URI} !/my_custom_subfolder/index.php$
  # Deny access to any other PHP files that do not match the rules above.
  # Specifically, disallow autoload.php from being served directly.
  RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F]

I also tried this change elsewhere in the .htaccess:

  # Pass all requests not referring directly to files in the filesystem to
  # index.php.
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteCond %{REQUEST_URI} !=/my_custom_subfolder
  RewriteRule ^ index.php [L]

Neither of these changes, either on their own or together, solved the problem.

Other people unable to run custom php seem to be prompted to download the file from the server, and aren't getting a 403 error, so possibly I have a different problem to the one I've read about many times, but I am out of ideas and would like to know if anyone else has had this problem or can offer advice.

Thanks!

Chris

Comments

stefan lehmann’s picture

To be honest I wouldn't try to run this custom script as part of the Drupal installation, if the generated output can be cached and doesn't have to be interactive or so.

I'd rather have that whole script installed under a completely separate vhost and would probably implement maybe a block or page template which retrieves the generated content by doing a local HTTP request and loads the content into the block, page or whatever.

That way Drupal and script are strictly separated and you can still sort out the mess later. But I'm unsure if that would even work for your use case.

chris5156’s picture

I'm not sure I understand why it's such a terrible problem to run a custom script in a subdirectory - it will not be reading or writing to the same database tables as Drupal and the database user/pass in the script do not allow it to even see them. It won't interact with Drupal in any way. It's just a result of the file structure that it will need to sit within the Drupal directory structure, because Drupal is in the root.

I don't have the luxury of alternative vhosts, unfortunately.

stefan lehmann’s picture

It's not a terrible problem .. It just sounds like a completely different application, so I'd try to keep them separate.

If that script just spits out some content, can't you just make it a module with some minor adjustments? If you have a look at the examples module, you'll find a block example. You could create a block which spits out the result of your custom script as the content of a block. That way you can even place it easily everywhere on your page.

onejam’s picture

I find the easiest way is to just create a subdomain for your subdirectory and use that url to access and execute your non-Drupal custom scripts.

Or you can try this: https://www.thesitewizard.com/apache/access-non-drupal-folders.shtml

-----------------------------------------------------------------
We build engaging websites and intuitive designs that will benefit your business.
Duvien

chris5156’s picture

Thanks for your reply.

I've now tried the subdomain option. This isn't ideal because it would change the URLs of the pages served up by my custom php script - and in any case, it still returns a 403 forbidden error, so it doesn't solve the problem. I have been trying a range of things in the .htaccess file but haven't found the answer. The php script and its parent directory both have their permissions set to 755 so they should not come up with a 403 error - I can only think something Drupal is getting in the way.

The guide you link to describes how to allow access to non-php files in the Drupal directory but not php.

antoniotorres’s picture

For anyone that reads this - The example provided by OP actually worked for me.

Make sure you aren't keeping cache or anything like that when you're working on .htaccess changes.

eyose’s picture

I have faced the same problem like you and I tried a lot of options but finally I found a solution for this. Put your custom php scripts in the directory in which index.php resides on. Make the permission also like index.php. It will work perfectly and the script will run as you need.

eisley79’s picture

Using Drupal 8.
Created a sub-folder in the installation ie: ./customphp/

PROBLEM:
-Initially all files ending in .php in the subfolder ./customphp/ return "403 Forbidden"

SOLUTION:
-Following the instructions and conversation in this thread, only ONE LINE needs to be added to the .htaccess file to correct.

**Add Line:

RewriteCond %{REQUEST_URI} !=/my_custom_subfolder

where in our example '/my_custom_subfolder' would be entered as '/customphp/'
** Place this in the .htaccess file, as shown in OPs comment, directly before the line

RewriteRule ^ index.php [L]

WHY IT WORKS
-This prevents all files in your custom folder, from being treated as part of the Drupal filesystem which is what is triggering the 403 Forbidden.
**Note, you can add multiple 'my_custom_subfolder' lines in the same manner, it does not need to be limited to just one.

Timotheos’s picture

The above didn't work for me. What did work was adding this one line.

 

# For security reasons, deny access to other PHP files on public sites.
# Note: The following URI conditions are not anchored at the start (^),
# because Drupal may be located in a subdirectory. To further improve
# security, you can replace '!/' with '!^/'.
# Allow access to PHP files in /core (like authorize.php or install.php):
RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
# Allow access to test-specific PHP files:
RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php
# Allow access to Statistics module's custom front controller.
# Copy and adapt this rule to directly execute PHP files in contributed or
# custom modules or to run another PHP application in the same directory.
RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
# Allow for my_custom_directory folder to run php
RewriteCond %{REQUEST_URI} !/my_custom_directory/[^/]*\.php$

# Deny access to any other PHP files that do not match the rules above.
# Specifically, disallow autoload.php from being served directly.
RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F]

ateigh’s picture

This worked for me, it was driving me crazy!. Thanks!
terracoders’s picture

This did the trick for me as well! Thanks for helping me avert long hours of frustration!

saradaprasad17’s picture

Yes, that worked for me . Thanks

joegl’s picture

Thanks for this -- it looks like the last line with the autoload is preventing PHP execution in any other paths and you have to manually define them in the .htaccess

For the search engine crawlers: this solved my 403 forbidden issue trying to access the /simplesaml directory when setting up SimpleSAMLPHP and SSO in Drupal

Shabeeba kalamban’s picture

It's working to me .but i want to run PHP files in all sub-folders of  my custom folder. is there any program can solve the issue without mention the sub folder name

joegl’s picture

Shabeeba -- I needed the same thing for a SimpleSAML setup so I changed this line:

RewriteCond %{REQUEST_URI} !/custom_folder/[^/]*\.php$

To just:

RewriteCond %{REQUEST_URI} !/custom_folder/

And it worked for me. However, I am going to review this again at some point to make sure it's not too open-ended.

yupang’s picture

I have found this URL from Google search. However, the solution didn't work for me. After research, I found that I need to add 2 lines instead of just 1.

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteCond %{REQUEST_URI} !script
  RewriteRule ^ index.php [L]

  RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php
  RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
  RewriteCond %{REQUEST_URI} !script
  RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F]

where script is the PHP Directory name.

rag_gupta’s picture

I added these two lines to make it work(line containing brsol and below it) in .htaccess:

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

RewriteCond %{REQUEST_URI} "/brsol/" RewriteRule (.*) $1 [L]

# Set "protossl" to "s" if we were accessed via https://. This is used later # if you enable "www." stripping or enforceme

At the same time /etc/apache2/sites-available/sitessl.conf contains this directive:

<Directory /home/home/public/www.mysite.in/public> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory>
 

Shabeeba kalamban’s picture

i can run run a php file in a custom folder in drupal by above given code

# Allow for my_custom_directory folder to run php
  RewriteCond %{REQUEST_URI} !/custom_folder/[^/]*\.php$

but i want to run PHP files in all subfolders of  my custom folder. is there any program can solve the issue without mention the sub folder name.

Gayathri T’s picture

Hi.. i am facing the same issue. Not able to run custom php scripts. Tried all the solutions but nothing worked. kindly help.posting the htaccess file..

My custom directory is "bdldesign"... Kindly help. have been trying from so long.

#
# Apache/PHP/Drupal settings:
#

# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^#.*#$|(~|\.sw[op]|\.bak|\.orig|\.save)$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
  </IfModule>
</FilesMatch>

<Files "login.php">
Allow from all
</Files>

# Don't show directory listings for URLs which map to a directory.
Options -Indexes

# Set the default handler.
DirectoryIndex index.php index.html index.shtml 

# Add correct encoding for SVGZ.
AddType image/svg+xml svg svgz
AddEncoding gzip svgz

# Most of the following PHP settings cannot be changed at runtime. See
# sites/default/default.settings.php and
# Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be
# changed at runtime.

# PHP 7, Apache 1 and 2.
<IfModule mod_php7.c>
  php_value assert.active                   0
</IfModule>

# Requires mod_expires to be enabled.
<IfModule mod_expires.c>
  # Enable expirations.
  ExpiresActive On

  # Cache all files for 2 weeks after access (A).
  ExpiresDefault A1209600

  <FilesMatch \.php$>
    # Do not allow PHP scripts to be cached unless they explicitly send cache
    # headers themselves. Otherwise all scripts would have to overwrite the
    # headers set by mod_expires if they want another caching behavior. This may
    # fail if an error occurs early in the bootstrap process, and it may cause
    # problems if a non-Drupal PHP file is installed in a subdirectory.
    ExpiresActive Off
  </FilesMatch>
</IfModule>

# Set a fallback resource if mod_rewrite is not enabled. This allows Drupal to
# work without clean URLs. This requires Apache version >= 2.2.16. If Drupal is
# not accessed by the top level URL (i.e.: http://example.com/drupal/ instead of
# http://example.com/), the path to index.php will need to be adjusted.
<IfModule !mod_rewrite.c>
  FallbackResource /index.php
</IfModule>

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

  # Set "protossl" to "s" if we were accessed via https://.  This is used later
  # if you enable "www." stripping or enforcement, in order to ensure that
  # you don't bounce between http and https.
  RewriteRule ^ - [E=protossl]
  RewriteCond %{HTTPS} on
  RewriteRule ^ - [E=protossl:s]

  # Make sure Authorization HTTP header is available to PHP
  # even when running as CGI or FastCGI.
  RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

  # Block access to "hidden" directories whose names begin with a period. This
  # includes directories used by version control systems such as Subversion or
  # Git to store control files. Files whose names begin with a period, as well
  # as the control files used by CVS, are protected by the FilesMatch directive
  # above.
  #
  # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is
  # not possible to block access to entire directories from .htaccess because
  # <DirectoryMatch> is not allowed here.
  #
  # If you do not have mod_rewrite installed, you should remove these
  # directories from your webroot or otherwise protect them from being
  # downloaded.
  RewriteRule "/\.|^\.(?!well-known/)" - [F]

  # If your site can be accessed both with and without the 'www.' prefix, you
  # can use one of the following settings to redirect users to your preferred
  # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
  #
  # To redirect all users to access the site WITH the 'www.' prefix,
  # (http://example.com/foo will be redirected to http://www.example.com/foo)
  # uncomment the following:
  # RewriteCond %{HTTP_HOST} .
  # RewriteCond %{HTTP_HOST} !^www\. [NC]
  # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
  #
  # To redirect all users to access the site WITHOUT the 'www.' prefix,
  # (http://www.example.com/foo will be redirected to http://example.com/foo)
  # uncomment the following:
  # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
  # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301]

  # 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 /

  # Redirect common PHP files to their new locations.
  RewriteCond %{REQUEST_URI} ^(.*)?/(install.php) [OR]
  RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild.php)
 # RewriteCond %{REQUEST_URI} !core
  RewriteCond %{REQUEST_URI} !vr
 RewriteCond %{REQUEST_URI} !=/bdldesign
  #RewriteRule ^ %1/core/%2 [L,QSA,R=301]

  # Rewrite install.php during installation to see if mod_rewrite is working
  #RewriteRule ^core/install.php core/install.php?rewrite=ok [QSA,L]

  # Pass all requests not referring directly to files in the filesystem to
  # index.php.
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteCond %{REQUEST_URI} !=/bdlsite/bdldesign
  RewriteRule ^ index.php [L]

  # For security reasons, deny access to other PHP files on public sites.
  # Note: The following URI conditions are not anchored at the start (^),
  # because Drupal may be located in a subdirectory. To further improve
  # security, you can replace '!/' with '!^/'.
  # Allow access to PHP files in /core (like authorize.php or install.php):
  #RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/bdldesign/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/^/vr/.*?
  # Allow access to test-specific PHP files:
  RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php
  RewriteCond %{REQUEST_URI} !/bdlsite/bdldesign/
  # Allow access to Statistics module's custom front controller.
  # Copy and adapt this rule to directly execute PHP files in contributed or
  # custom modules or to run another PHP application in the same directory.
  #RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
  RewriteCond %{REQUEST_URI} !/vr/*.php$
  # Deny access to any other PHP files that do not match the rules above.
  # Specifically, disallow autoload.php from being served directly.
 # RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F]

  # Rules to correctly serve gzip compressed CSS and JS files.
  # Requires both mod_rewrite and mod_headers to be enabled.
  <IfModule mod_headers.c>
    # Serve gzip compressed CSS files if they exist and the client accepts gzip.
    RewriteCond %{HTTP:Accept-encoding} gzip
    RewriteCond %{REQUEST_FILENAME}\.gz -s
    RewriteRule ^(.*)\.css $1\.css\.gz [QSA]

    # Serve gzip compressed JS files if they exist and the client accepts gzip.
    RewriteCond %{HTTP:Accept-encoding} gzip
    RewriteCond %{REQUEST_FILENAME}\.gz -s
    RewriteRule ^(.*)\.js $1\.js\.gz [QSA]

    # Serve correct content types, and prevent double compression.
    RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=no-brotli:1]
    RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=no-brotli:1]

    <FilesMatch "(\.js\.gz|\.css\.gz)$">
      # Serve correct encoding type.
      Header set Content-Encoding gzip
      # Force proxies to cache gzipped & non-gzipped css/js files separately.
      Header append Vary Accept-Encoding
    </FilesMatch>
  </IfModule>
</IfModule>

# Various header fixes.
<IfModule mod_headers.c>
  # Disable content sniffing, since it's an attack vector.
  Header always set X-Content-Type-Options nosniff
  # Disable Proxy header, since it's an attack vector.
  RequestHeader unset Proxy
</IfModule>