2

I'm hosting a node app and my folder structure is like this:

mydomain.com
--index.js
--package.json
--public\test.txt

I want to treat public as my document root and not allow anything above it to be accessed e.g. package.json.

I'm currently using .htaccess to change the "document root":

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

This works fine and I can access http://example.com/test.txt, but it also means that http://example.com/public/test.txt also works.

How can I prevent public from being directly used in the URL and only used behind the scenes?

I've tried adding RewriteRule ^public/ - [L,R=404] to the end, but end up returning 404 for everything.

2
  • Why not simply edit your httpd.conf and change the DocumentRoot if you want to use a different directory layout? Rewrite rules add needless complexity and compute penalties to every request....
    – HBruijn
    Dec 1, 2018 at 10:09
  • 1
    I'm on shared hosting, so I don't believe that I can edit that file.
    – row1
    Dec 2, 2018 at 1:20

1 Answer 1

2

Just to clarify, you're not actually changing the document root here, you are effectively hiding a subdirectory using URL rewriting. To actually change the document root, you need to change the DocumentRoot directive in the server config (as mentioned in comments) - which will have other implications (your node app is probably dependent on the DocumentRoot being set as it is).

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

This checks for the absence of "public/" anywhere inside the requested URL-path, which is not really what you require. However, the RewriteCond directive is not necessary here. You should instead be checking the URL-path in the RewriteRule pattern. For example:

 RewriteRule !^public/ /public%{REQUEST_URI} [L]

Note that the REQUEST_URI server variable includes the slash prefix. (Which maybe why you removed the anchor in your CondPattern in order to get it to "work"?)

I've tried adding RewriteRule ^public/ - [L,R=404] to the end, but end up returning 404 for everything.

That's the right idea, however, by itself this will also redirect rewritten URLs (by the other directive). You need to only target direct requests. There are several ways to do this. My preference is to check the REDIRECT_STATUS environment variable, which is empty initially and set to "200" after the first successful rewrite. For example:

RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^public($|/) - [R=404]

($|/) - Note that I've also made the trailing slash "optional", in that it will match either public or public/ (using alternation) when accessing the bare directory. This is to avoid mod_dir from first issuing a 301 redirect to append the trailing slash on the directory, before sending a 404 in response to the redirected (2nd) request. The alternation (end-of-string or slash) ensures that public/<foo> is matched, but not public<foo>, should you have URLs that start "public" (eg. /public-enemy).

There is no need for the L flag when using a non 3xx (or 2xx) status code as it is implied.

In summary:

# Block direct requests to the "public" subdirectory
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^public($|/) - [R=404]

# Rewrite all requests that are not already for the "public" subdirectory
RewriteRule !^public/ /public%{REQUEST_URI} [L]

Note that the blocking (404) directive should be first.

2
  • Thanks for the detailed answer. This appears to be working and a viable solution if my host can't help me out. The server default 404 never seems to be displayed and instead the node app gets invoked and returns 200 -- this might be OK though as the app can issue the 404.
    – row1
    Dec 2, 2018 at 1:36
  • Perhaps you have an ErrorDocument 404 ... defined that calls the node app? (If you're on shared hosting then I'm pretty sure you won't be able to change the document root. The other option is to "simply" move everything up a directory, as you usually have access to a directory above the document root anyway.)
    – MrWhite
    Dec 2, 2018 at 10:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .