yum
upgrades for production use, this is the repository for you.
Active subscription is required.
Being an avid StackExchange user, I could see how many users completely lack an understanding of the proper permissions model in the most popular, LEMP stack.
You can see recommendations that 777 is never good, but I could not see a simple guide on permissions that can be used as a reference point for everyone.
The following permissions/ownership model applies to all NGINX/PHP-FPM websites and allows you to host websites without any problems, in a secure way.
PHP-FPM user (as known as the website user)
The PHP-FPM user should be a special user that you create for running your website, whether it is Magento, WordPress, or anything.
Its characteristics are the following:
- This is the user that PHP-FPM will execute scripts with
- This user must be unique. Do not reuse an existing user account. Create a separate user for each website!
- Do not reuse any
sudo
capable users. If your website user isubuntu
orcentos
, or,root
– you’re asking for much trouble. - Do not use
www-data
ornginx
as website user. This is wrong and will lead to more trouble! - The username should reflect either the domain name of the website that it “runs”, or the type of corresponding CMS, e.g.
magento
for a Magento website; orexample
forexample.com
website.
Let’s create this user:
useradd example
Now, set its password by running:
passwd example
Each website in PHP-FPM should be run under a separate pool. In the pool settings file, e.g. /etc/php-fpm.d/example.com.conf
, you must set things to match with the created username:
listen = /var/run/php-fpm/example.com.sock
listen.owner = example
listen.group = example
listen.mode = 0660
user = example
group = example
The webserver user
NGINX must run with it own unprivileged user, which is nginx
(RHEL-based systems) or www-data
(Debian-based systems).
This is the “global” webserver user that is used for all websites. So the configuration is straightforward and translates to the following directives in /etc/nginx/nginx.conf
:
user nginx;
Connecting website and webserver users
Now the magic. We must connect things up so that NGINX (webserver) user can read files that belong to the website user’s group.
This will allow us to control what NGINX can read or not, via group chmod permission bit.
usermod -a -G example nginx
This reads as: add nginx
user to group example
.
File ownership (chown
)
Here is a simple rule: all the files should be owned by the website user and the website user’s group:
chown -R example:example /path/to/website/files
Incorrect chown
examples:
www-data:www-data
example:nginx
nginx:nginx
Correct chown
example:
example:example
foo:foo
Permissions (chmod
)
The following general chmod
setup will allow for any website to function properly:
chmod -R u=rwX,g=rX,o= /path/to/website/files
This translates to the following:
- Website user (
example
) can read, write all files, and read all directories - Website group (webserver user) can read all files and traverse all directories, but not write
- All other users cannot read or write anything
In octal notation, this results in 0750
chmod for all directories and 0640
for all files.
If you simply stick to this permissions model, you will not encounter any chmod
/ chown
issues in the future.
This is the only proper model, functionality, and security-wise.
These general permissions are quite secure already out of the box. But of course, if you know your website structure, you may want to tighten up permissions even further.
For example, see Magento 1.x lockdown chmod.
A read-only user
When you want to provide a read-only access for someone else, you can leverage setfacl
program. For example, to allow user foo
to read a directory and all files inside:
setfacl --recursive --modify u:foo:rX,d:u:foo:rX /path/to/website/files
Troubleshooting tips
To see all directory and file permissions, up to a specific path, use this command:
namei -om /path/to/file/or/directory
Shnoulle
With
chmod -R u=rwX,g=rX,o= /path/to/website/files
,If you have assets in
/path/to/website/files/assets/css
for example : nginx can not read/load it …How do you manage this?
A potential solution ACL for nginx/www-data user.
Danila Vershinin
NGINX will be able to read the files.
css
directory will be owned byexample:example
(owned byexample
user and byexample
group). Because we addednginx
user (orwww-data
, depends on distro) as a member of the site user group, and the directory chmod allows for group reads, it will be able to access the directory just fine.See the “Connecting website and webserver users” section. This is the essential step: the webserver user is a member of each website’s user group. Not the other way around.
All the files are owned by
example:example
. Where necessary for NGINX to read the files, the group permission bit is used to control what it can and what it can’t read.Steve M
Danila,
Thank you for your article. I followed your instructions and configured nginx and php-fpm to work the same way, but I have issues with stylesheets. I have WordPress installation and stylesheets and other statics aren’t loaded properly. In browser’s console it says “Resource interpreted as Stylesheet but transferred with MIME type text/html”. I figured it’s statics going through php-fpm and not straight through nginx. I still haven’t got a clue how to fix it though.
Danila Vershinin
Your error rather indicates a case when the browser receives a 404 (not found) or similar error, generated by NGINX, when accessing
/some/style.css
or/some/script.js
. Those haveContent-Type: text/html
, which is of course neither a stylesheet nor a script.It doesn’t matter here WordPress or not you’re dealing with. The above permission setup applies to any NGINX/PHP-FPM website without any flaws or problems.
Your issue means that NGINX cannot read the files. You haven’t followed some of the steps as outlined above. Make sure
nginx
user in website user’s group and the permissions were set aschmod -R u=rwX,g=rX,o= /path/to/website/files
suges
It really helped a lot, thank you, Danila.
Person
What about using Linux ACLs? No need to modify the owner of the files, but still allow access to any folder that needs it.
This is an example for Laravel: https://stackoverflow.com/a/41268166/12869323
Danila Vershinin
Bear with me 🙂 You really meant “no need to set the user ownership and permissions right”.
In the case of NGINX (which can be applied to Apache as well, depending on a mode it runs with) and PHP-FPM, there are basically 2 services interacting with each other: NGINX is one user, PHP-FPM is another.
Two services mean two different Linux users. They can be connected through the group permission bit.
“Connected” means both of the mentioned services (users) can access/modify the files without giving problems to each other.
Funnily enough, as far as your referenced StackOverflow question, the accepted/most-upvoted answer almost got it right.
But never far enough right. At least for any of the stacks of “Apache + PHP-FPM” or “NGINX + PHP-FPM,” it won’t be right:
In the section “Webserver as owner (the way most people do it, and the Laravel doc’s way)”, even its title is wrong.
I don’t think there is such a reference in Laravel doc that would dare to state that all files should be owned by
www-data
,which is outright wrong.
Instead, the right thing: every website has its own dedicated username (which PHP-FPM pool runs with),
and all the particular website files are owned by their respective website user.
The correct part in that section was about connecting users through adding
www-data
to the user’s group.As far as
chmod
, any of the 755 or 644 permissions will allow any Linux user on the server to read anyone’s website files.Those should be 750 and 640 max, unless you really want to grant access to all Linux users on the system
(which in the context of website files, I think is a case of “never”).
In the section “Your user as owner” now it’s incorrect the other way around.
The files are now owned correctly by the dedicated “website user”, but he does have to identify
which directories have to be written to by the Apache user
www-data
. Likely so because the question itself is aboutApache running with mod_php, which is now more or less deprecated way to run PHP in the first place.
As for the referenced answer. Only when you have 3 users, or when you’re using Apache+mod_php – only then
setfacl
may be applicable. Otherwise using it is only a patch on top of an often incorrect simple permission model ofchown
/chmod
.If you’re running PHP as an Apache module, then there are 2 users, but there is a more severe disconnection between who often manages/uploads the files (SFTP user, human being), and who reads/executes PHP scripts among those files (
www-data
, Apache user).The default Linux mask usually results in the new directories/files created with group chmod that allows for reading only.
But since in this setup sometimes directories are being created by the webserver user (as a result of running scripts/web installs, etc.),
those created directories only end up being readable to the human being who cannot change/delete such directories.
Then (again, for Apache+mod_php) the solution can easily be to set
umask 002
for the Apache, e.g.:And then all the files/directories created by it will be readily writable to the
www-data
group. And to finalize this approach for this kind of setup, you’d do the magic part as described above, just in the other way around: add the SFTP user to thewww-data
group. That’s it, nosetfacl
s.The
setfactl
is great when there are really three users. Having three distinct users working on the same set of files pretty much introduces requirement to use it because chmod has only 3 bits and the last bit is for “all others”.I say, why would you want to use
setfacl
if you haven’t managed to reap the value of even the first 2 bits of the basic Linux permission system.Amit Saini
Hello Danila Vershinin,
Thanks for shared this useful information. I have installed magento2 on private server wth ubuntu and nginx. I have created a new user and make changes as per you guide. But I want to know by which user I need to run the command process? Because magento root files owner is new user that I have created, but this user has not root user permissions. So, for commands another user called “ubuntu” is used or not?
Danila Vershinin
It’s pretty straightforward: for interacting with your website, like running SSH command
magento
or working with files over SFTP – use your website user.Use your
ubuntu
user (or any other user with sudo privileges) only for administrating your server. That is, for example, installing or upgrading server packages. Do not use yourubuntu
user for interacting with your website files.CM
GREAT explanation!!!
It helped me a lot.
Thanks
I believe there’s one additional setting required:
The same value assigned to ‘listen’ in the pool settings file must be assigned to ‘fastcgi_pass’ in the Nginx Server Block.
PHP-FPM pool config:
listen = /var/run/php-fpm/example.com.sock
Nginx Server Block
fastcgi_pass unix:/var/run/php-fpm/example.com.sock
(using unix socket in this example)
iddqd_x
Hi Danila! Your post is the clearest I’ve seen on the net. Everything works fine, but I don’t understand how to safely give additional user account access to the website files. All files owned by example:example, I’ve added user foobar and added him to example group (nginx user also in the group example). So in this case I should use chmod 760 right? How safe this configuration? Or it can be achieved in another way? And probably I should also set SGID (sticky bit) attribute? Thank you.
Danila Vershinin
I’d say when you really need to give access to another account (besides the site user and nginx), then use
setfacl
(extended ACL for Linux).This will let you tune access beyond the bits provided by
chmod
.However, if it’s being done for the sake of “adding developers” for a website, or other project management, this is still bad.
For this, a git repository is most appropriate. Developers should not have access to the live system.
iddqd_x
I’m really appreciate for your so fast answer. The situation is pretty standard. I have one user “example” (it owns all files and fpm runs by this account). And every developer use this single account to access ssh. Each developer has its own git account to push/pull changes. But this situation is not secure by design when developers user same ssh account.
So my goal is to restrict access to “example” account and provide unique ssh account for each developer and keep the owner of the files by “example” user/group. What is your opinion about “-o” flag for “useradd” command which creates user account with non-unique UID? I suppose that would do the trick but how safe it is. In this case I don’t need to set higher chmod as developers accounts will have same UID/GID as “example” user.
Danila Vershinin
Likewise, my answer is the same. Don’t let developers SSH at all. There should not be “their account”, neither they should access the live account. If you have the time to puzzle your head with how to give them access, you likely have also the time to set up website updating through git hooks. A good developer SSH account on your server is none 🙂 The only thing they would have access to is git and nothing else. Development by a sane developer happens in their local machine. Not on a server of any kind (not even on dev server – that is for integration/testing).
Dibs
So, I did this, though I am not certain I FULLY understand it…
All seems to be running fine but nginx -t throws some errors now.
These are permission issues on /var/log/nginx/error.log and my letsencrypt certs – I don’t really understand this as user:nginx is still running the web server?
Danila Vershinin
What are the exact word to word errors you receive?
Dibs
My error was being a moron and running
nginx -t
without proper privledgesPhil T.
This means “webserver user” has no write access to ‘wp-content/*’, right? So, is sftp or ssh required for plug-in management etc in WordPress?
Danila Vershinin
No, it means webserver user (
nginx
) can read to the wp-content, but can’t write; and website user (example
) can do both.Both SSH and WP-admin management of plugins will be functional.
Jake
How would you apply this method to Apache?
Danila Vershinin
Uninstall Apache, then install NGINX. 🤭
vseager
I have followed your guide and it’s working for my Magento 2 installation, however when I run command line commands I run into problems where they seem to be running as www-data. For example, when I run
bin/magento setup:di:compile
I get the following error: “The requested class did not generate properly, because the ‘generated’ directory permission is read-only.”.Before running the command I have tried resetting ownership recursively on the whole Magento directory to example:example, and I’m running the command when logged in as example, but the
bin/magento
command always seems to be running as www-data.Danila Vershinin
It only means that either your server is misconfigured and still runs as www-data and/or that you’re not running chown command as root. It should be run with root, simply because you can’t have one user take over ownership of other users files. Root user can 😏
vseager
Thanks Danila, I’ll double check server configuration. I’m running chown with
sudo
zaries
Thanx your a life saver!
Rich
I’m still getting
403 Forbidden
errors using your instructions. New user for a site. I’m usingpassenger
withnginx
but that shouldn’t change anything, correct? What should I try? I’m trying to run these in my/home/myuser/sites
directory, so maybe its parents are standing in the way?Danila Vershinin
Home directories typically have permissions of
0700
, so that would surely be standing in a way unless you put it0750
at least. For that reason, I am not recommending the use of home dirs. The canonical location, in my opinion, for web files, is/srv/www/example.com/public
(you need to create all the directories along the way, and ensure they are each at least 0750).Rich
I’m on
AlmaLinux 8.6
which currently shows the following (default path to nginx sites):[2022_Aug_16 11:04:39 user3@server_f ~] namei -om /usr/share/nginx/html/vhost1
f: /usr/share/nginx/html/vhost1
dr-xr-xr-x root root /
drwxr-xr-x root root usr
drwxr-xr-x root root share
drwxr-xr-x root root nginx
drwxr-xr-x root root html
drwxr-xr-x root root vhost1
Not sure how that’s going to show up here in the comments.
nginx -t
shows all good. But still getting403
:2022/08/16 11:07:33 [error] 73646#0: *1730 directory index of "/usr/share/nginx/html/vhost1/" is forbidden, client: 192.168.1.4, server: vhost1.com, request: "GET / HTTP/1.1", host: "www.vhost1.com"
Not sure why it’s asking for a
directory index
.Danila Vershinin
Provided that you’ve already set up everything as in the article, the
vhost1
directory should be owned by your PHP-FPM user and group, so you willchown -R example:example vhost1
. NGINX is basically telling you it can’t look inside/usr/share/nginx/html/vhost1
for finding an index file likeindex.html
, etc. In other words, it can’t browse/can’t see that directory’s contents.Andreas
Wow, this is really cool, makes total sense and works like a charm. Thanx
jackso
Nice writeup and glad I found this guide. Unfortunately I ran into issue with bootstrap folder and storage folders with nginx
drwxrwx--- 3 test1 test1 4096 Nov 30 12:44 bootstrap
drwxrwx--- 7 test1 test1 4096 Nov 30 12:46 storage
Here is my setup:
/var/www/app1 <-- user = test1
/var/www/app2 <-- user = test2
If I use chmod -R u=rwX,g=rwX,o= instead of the one listed here then it works with bootstrap and storage folder. Maybe I missed something? I went through the steps few times already.
Danila Vershinin
It’s not clear what “works” in your case and what’s not. What’s the actual issue with those folders? Have you set up PHP-FPM pools to run as those users?
jackso
Will try to explain. Hope it makes sense.
Using
chmod -R u=rwX,g=rX,o=
as you suggest will give me thisdrwxr-x---
resulting in folders (storage and bootstrap) unwritable error. Laravel log showing: can’t write to path /var/www/app1/storage/…php8.1-fpm test1.conf
[www]
user = test1
group = test1
listen = /run/php/php8.1-fpm.sock
listen.owner = test1
listen.group = test1
listen.mode = 0660
I’m unable to convert to your method so have to go back to how most docs suggesting the following:
sudo chgrp -R www-data storage bootstrap/cache
sudo chmod -R ug+rwx storage bootstrap/cache
result: drwxrwx---
Anything else you suggest I check?
Danila Vershinin
The leading
drwx
in your chmod means that the user owner of the file/directory can do anything with it (read and write). So there shouldn’t be any problem.Unless: 1) you haven’t configured the correct PHP-FPM pool definition with
listen.owner=test1
2) you haven’tchown
ed the files/directories to thetest1
user. All should betest1
.sanjeev
If i dont want to create PHP-fpm pool and i dont want to create new user and groups.. I want to use default pool so do i need to set file permission to www-data:www-data replacing example:example.
chown -R example:example /path/to/website/files
Danila Vershinin
If you don’t want to create a separate PHP-FPM pool/users, then you’re doing it wrong, then you can continue doing everything the wrong way, no more no less 🙂
Tobias
Why the “usermod -a -G example www-data” way? The follow is easier i think:
PHP-FPM
listen = /var/run/php-fpm/example.com.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
user = example
group = www-data
File permissions
chown -R example:www-data /path/to/website/files
chmod 750 to all directories
chmod 640 to all files
Result:
Website user have full read/write access and nginx (www-data) just read access. Your opinion?
Danila Vershinin
Because then any user from any website can at minimum read each other websites’ files. Furthermore, it’s just illogical: you run PHP-FPM with webserver’s user. These are 2 separate daemons. Would you swap names to another person? Same thing 🙂
Andreas
Thank you for this great tutorial. I use this approach since I came across it on your website.
But I am still struggling to integrate my deployment flow. When I have to update, I change the user to my github user, then cd into the directory, make a git pull, make a npm run build and then switch user and group again. And during that time, the website is down. Even though this process does not take long, it is a bit frustrating.
Do you have any suggestion for a deployment without downtime?