CresignSys Learn — Lesson 062

Written by

in

Linux Users, Groups & Permissions for Multi-Website Hosting

We now connect the three major security layers:

Nginx
   ↓
PHP-FPM
   ↓
Linux permissions
   ↓
Website files

The goal is:

Site 1 should not be able to modify Site 2’s files.

This is one of the most important concepts in a hosting platform.


1. Why Linux Permissions Matter

Suppose your server contains:

/storage/websites/
├── site1.com/
├── site2.com/
└── site3.com/

If all websites run with the same Linux identity:

www-data

then a compromised application may potentially access files belonging to other sites that www-data can access.

A better model is:

site1 → user1
site2 → user2
site3 → user3

2. Linux User

A Linux user has an identity.

Example:

site1

The operating system assigns it a:

UID

UID means:

User ID

For example:

site1 → UID 1001
site2 → UID 1002

The actual numbers are assigned by the system and should not be hard-coded in your automation without checking.


3. Linux Group

A group is a collection of users.

Example:

site1
 └── group: site1

site2
 └── group: site2

Groups allow you to control shared access to files and directories.


4. User + Group

A file may have:

owner
group
permissions

Example:

site1:site1

means:

owner = site1
group = site1

5. Check Your User

Run:

id

You may see:

uid=1000(username) gid=1000(username) groups=...

For another user:

id site1

6. Create a Hosting User

For example:

sudo useradd -m -s /bin/bash site1

This creates a Linux user.

However, for a production hosting platform, your creation script should explicitly define the account model rather than blindly using commands like this.


7. Check the User

id site1

You should see the user’s:

UID
GID
groups

8. User Home Directory

If created with -m, the user normally gets a home directory:

/home/site1

For your hosting architecture, website files can instead live under:

/storage/websites/site1.com/

9. Ownership

Check ownership with:

ls -la

For a website:

ls -la /storage/websites/site1.com

You may see:

site1 site1

or another ownership model depending on your configuration.


10. ls -l

Example:

-rw-r--r-- 1 site1 site1 1234 index.php

Break it down:

-rw-r--r--
│
├── file type
├── owner permissions
├── group permissions
└── other permissions

11. The Three Permission Classes

Linux permissions are divided into:

User
Group
Other

Often abbreviated:

u
g
o

Example:

u = owner
g = group
o = everyone else

12. Read

r

means:

Read the contents.

For a file:

r = 4

13. Write

w

means:

Modify the contents.

Numeric value:

w = 2

14. Execute

x

means:

Execute the file.

Numeric value:

x = 1

For directories, x has an important additional meaning: it permits traversal/searching through the directory.


15. Numeric Permissions

Combine:

r = 4
w = 2
x = 1

Therefore:

rwx = 7
rw- = 6
r-x = 5
r-- = 4
-wx = 3
-w- = 2
--x = 1
--- = 0

16. 755

A very common permission mode:

755

means:

Owner  = rwx = 7
Group  = r-x = 5
Other  = r-x = 5

So:

rwxr-xr-x

17. 644

Another common mode:

644

means:

Owner  = rw- = 6
Group  = r-- = 4
Other  = r-- = 4

So:

rw-r--r--

18. Why Files and Directories Differ

For a file:

r

means read its contents.

For a directory:

r

means list directory entries.

For a directory:

x

means traverse/search the directory.

This distinction is essential when troubleshooting WordPress permissions.


19. Example Directory

Suppose:

/storage/websites/site1.com/public

has:

755

Then:

site1

can:

read
write
enter

while other users may be able to:

read
enter

but not modify its contents.


20. File Permissions

Suppose:

index.php

has:

644

Then the owner can:

read
write

while group and others can:

read

but not modify it.


21. chmod

chmod changes permissions.

Example:

chmod 644 index.php

or:

chmod 755 public

22. Recursive Permissions

You can use:

chmod -R ...

but be careful.

Running:

chmod -R 777 /storage/websites/site1.com

is a bad hosting practice.

It gives excessively broad permissions.


23. Why 777 Is Dangerous

777

means:

owner  rwx
group  rwx
other  rwx

Everyone can potentially modify the files.

For a web hosting environment this can create serious security problems.

Avoid using:

chmod -R 777

as a generic solution to permission errors.


24. chown

chown changes ownership.

Example:

sudo chown site1:site1 index.php

Now:

owner = site1
group = site1

25. Recursive chown

You can change an entire site:

sudo chown -R site1:site1 /storage/websites/site1.com

But again, don’t blindly apply recursive ownership changes to directories containing files that intentionally need different ownership.

Understand the desired ownership model first.


26. The Basic Hosting Model

For independent sites:

site1.com
owner = site1
group = site1

site2.com
owner = site2
group = site2

Then:

site1

should not automatically have write access to:

site2.com

27. Test Isolation

Create:

/storage/websites/site1.com/test.txt

owned by:

site1:site1

Then attempt access as site2:

sudo -u site2 cat /storage/websites/site1.com/test.txt

If permissions are correctly isolated, site2 should be denied.

This is an excellent security test.


28. Don’t Test as Root

Remember:

root

can bypass many normal permission restrictions.

Therefore:

sudo -u site2 ...

is useful for testing what the actual hosting user can do.


29. PHP-FPM User

Now connect this to the previous lesson.

Suppose:

site1 PHP-FPM pool

runs as:

site1

Then PHP code executed by that pool has the Linux identity:

site1

This is extremely important.


30. The Security Chain

For Site 1:

Browser
 ↓
Nginx
 ↓
PHP-FPM pool 1
 ↓
Linux user site1
 ↓
Site 1 files

For Site 2:

Browser
 ↓
Nginx
 ↓
PHP-FPM pool 2
 ↓
Linux user site2
 ↓
Site 2 files

31. What Happens During a Compromise?

Suppose:

site1.com

has a vulnerable plugin.

An attacker manages to execute PHP code through that application.

The PHP process should ideally run as:

site1

Therefore the attacker’s access is constrained by the permissions of:

site1

rather than a broad shared account.

This is not a complete security guarantee, but it significantly improves isolation.


32. Site 1 Should Not Own Site 2

Bad model:

site1
site2
site3
      ↓
www-data

Better model:

site1 → site1
site2 → site2
site3 → site3

33. Nginx Complication

Nginx workers may still run as:

www-data

So Nginx needs enough access to:

read static files
traverse directories
access PHP-FPM sockets

without gaining unnecessary write access.

This requires careful permissions.


34. Important Principle

A good architecture is:

Nginx
→ read-only access to website files where possible

PHP-FPM
→ write access only where the application needs it

This is safer than giving the web server universal write access.


35. WordPress Needs Some Write Access

WordPress may need to write to:

wp-content/uploads/

and sometimes other directories depending on plugins and update methods.

Therefore you should not blindly make the entire WordPress installation writable.


36. Better WordPress Permission Model

Conceptually:

WordPress core
→ mostly read-only

themes
→ mostly read-only

plugins
→ mostly read-only

uploads
→ writable where needed

The exact model depends on how WordPress updates and plugin management are handled.


37. Why wp-content/uploads?

When you upload:

image.jpg

through WordPress:

WordPress
 ↓
PHP
 ↓
wp-content/uploads/
 ↓
image.jpg

Therefore the PHP process must have appropriate write access.


38. But Don’t Make Everything Writable

Avoid:

chmod -R 777 public

Instead, identify exactly what needs to be writable.

For example:

public/
├── index.php
├── wp-admin/
├── wp-includes/
├── wp-content/
│   ├── plugins/
│   ├── themes/
│   └── uploads/

The required write permissions should be deliberately designed.


39. WordPress Updates

There is an architectural decision:

Model A

PHP can modify WordPress core/plugin files.

Advantages:

easy automatic updates

Disadvantages:

larger write surface

Model B

Core/plugin files are more restricted.

Updates are performed through controlled deployment/administrative mechanisms.

Advantages:

stronger security

Disadvantages:

more management complexity

For a hosting platform, this tradeoff matters.


40. Directory Traversal

Suppose:

/storage/

has restrictive permissions.

Even if:

public/

is readable, PHP may still fail if the process cannot traverse the parent directories.

Remember:

directory x permission
=
ability to traverse/search

41. Example

Suppose:

/storage

is:

700 root:root

Then:

site1

may not be able to reach:

/storage/websites/site1.com/public

even if the final directory is:

755

The entire path matters.


42. Check Every Level

Use:

namei -l /storage/websites/site1.com/public

This is an extremely useful command.

It shows permissions and ownership for each component of the path.


43. Example

Conceptually:

f: /storage/websites/site1.com/public
drwxr-xr-x root root storage
drwxr-xr-x root root websites
drwx------ site1 site1 site1.com
drwxr-xr-x site1 site1 public

Now you can identify exactly where traversal fails.


44. umask

umask controls default permission restrictions applied when new files/directories are created.

Check it:

umask

You might see:

0022

or another value depending on the process/user.


45. Why umask Matters

When PHP creates:

newfile.txt

the resulting permissions depend partly on:

requested mode
+
umask

Therefore your hosting environment should have predictable defaults.


46. Setgid Directory

A directory can use the setgid bit.

For example:

chmod 2770 directory

The leading:

2

sets the setgid bit.

For directories, this can cause newly created files/directories to inherit the directory’s group.

This can be useful when multiple trusted processes/users need to share a group.


47. Why Setgid Can Help

Suppose:

site1

has:

group = site1

and multiple processes need to cooperate through that group.

A setgid directory can help maintain consistent group ownership.

But don’t add it everywhere without a clear group-sharing requirement.


48. ACL

Linux also supports:

Access Control Lists

ACLs allow more granular permissions than the basic:

owner
group
other

model.

For example:

site1

could have special access without changing the primary group model.


49. Check ACL

If ACLs are installed and used:

getfacl /storage/websites/site1.com

You may see:

user::...
group::...
other::...

plus additional entries.


50. setfacl

ACLs can be modified with:

setfacl

For example, you can grant a specific user read access without changing ownership.

However, for your hosting platform, start with normal Unix ownership and permissions.

Add ACLs only when there is a real requirement.


51. Recommended Starting Model

For each site:

Linux user = site1
Primary group = site1
Website ownership = site1:site1
PHP-FPM pool = site1
PHP worker user = site1
Database = site1_database
Database user = site1_dbuser

This gives a consistent identity across the system.


52. Directory Structure

Use:

/storage/websites/
└── site1.com/
    ├── public/
    ├── logs/
    ├── backups/
    └── private/

Only:

public/

should be exposed through Nginx.


53. Public Directory

Example:

/storage/websites/site1.com/public

contains:

index.php
wp-admin/
wp-content/
wp-includes/

Nginx:

root /storage/websites/site1.com/public;

54. Private Directory

You could maintain:

/storage/websites/site1.com/private

for internal files.

Do not configure Nginx to serve it.

For example:

private/
├── backups/
├── temporary/
└── configuration/

55. Logs

Site-specific logs can be stored separately:

/storage/websites/site1.com/logs/

But be careful with ownership and Nginx’s ability to write logs.

An alternative is to keep logs under:

/var/log/nginx/

with appropriate per-site log configuration.


56. Backup Directory

Backups should ideally not be directly web-accessible.

Bad:

public/backups/database.sql

because someone could potentially request:

https://site1.com/backups/database.sql

if the file is exposed.

Better:

/storage/backups/site1/

outside the web root.


57. Database Credentials

Similarly, never store a backup such as:

database.sql

inside the public web root.

And never expose:

.env
backup.zip
database.sql

through HTTP.


58. Site Isolation Test

Suppose:

site1
site2

exist.

Test:

sudo -u site1 touch /storage/websites/site1.com/public/test1.txt

This should succeed.

Then:

sudo -u site1 touch /storage/websites/site2.com/public/test2.txt

This should fail if site2 is properly isolated.


59. Test PHP Identity

Create a temporary PHP diagnostic file carefully:

<?php
echo get_current_user();

However, get_current_user() isn’t necessarily the best way to determine the OS process identity.

For an actual process identity, you can use server-side diagnostics carefully, then delete the diagnostic file immediately after testing.

Never leave debugging information exposed on a production website.


60. Better Conceptual Test

The important question is:

Which Linux user is executing PHP?

If Site 1’s PHP process runs as:

site1

then its filesystem operations are governed by Site 1’s permissions.


61. Nginx and PHP-FPM Relationship

Your final permission model could look like:

                    NGINX
                      │
             reads public files
                      │
                      ▼
                 PHP-FPM
                      │
          ┌───────────┴───────────┐
          ▼                       ▼
       site1                    site2
       user                     user
          │                       │
          ▼                       ▼
      site1 files             site2 files

62. What Site 1 Should NOT Access

Site 1 should not have write access to:

site2/public/
site2/private/
site2/backups/
site2/configuration/
site2/database credentials

unless explicitly required.


63. What Root Can Access

Remember:

root

is different.

A hosting administrator/root account can access all sites.

Therefore:

customer isolation
≠
administrator isolation

Your goal is tenant-to-tenant isolation.


64. Linux Security Is Layered

Do not depend on only one mechanism.

Use:

Linux users
+
groups
+
filesystem permissions
+
PHP-FPM pools
+
database users
+
Nginx isolation
+
firewall
+
application updates

65. A Complete Site Identity

For:

learn.cresignsys.com

you can conceptually define:

SITE ID
learn

DOMAIN
learn.cresignsys.com

LINUX USER
learn

LINUX GROUP
learn

WEB ROOT
/storage/websites/learn.cresignsys.com/public

PHP-FPM POOL
learn

PHP SOCKET
/run/php/learn.sock

DATABASE
learn_cresignsys

DATABASE USER
learn_dbuser

This is an excellent pattern for automation.


66. Why Consistent Naming Matters

Your scripts become much easier if everything follows a predictable naming scheme.

For example:

Domain:
learn.cresignsys.com

Linux user:
learn

PHP pool:
learn

Socket:
learn.sock

Database:
learn_db

Database user:
learn_dbuser

For domains with unusual characters, your automation should sanitize and generate safe identifiers rather than using arbitrary domain strings directly.


67. Don’t Use Domain as a Linux Username Blindly

A domain may contain:

www.example.com

but system account naming rules and your platform’s conventions may make a normalized identifier preferable.

Your provisioning script should generate a safe unique site ID.

Example:

SITE_ID = learn_cresignsys_com

or another collision-resistant identifier.


68. Hosting Control Panel Data

Eventually your control panel database might contain:

sites

with:

site_id
domain
linux_user
document_root
php_version
php_pool
database_name
database_user
ssl_status
created_at

Then your automation can generate the actual server configuration.


69. Control Plane → Server

Conceptually:

Hosting Panel
      ↓
Site record
      ↓
Provisioning script
      ↓
Linux user
      ↓
Directories
      ↓
PHP-FPM
      ↓
Nginx
      ↓
MySQL
      ↓
SSL

This is the beginning of a real hosting control plane.


70. Permission Troubleshooting

When WordPress says:

Unable to create directory

don’t immediately run:

chmod -R 777

Instead ask:

Which user is PHP running as?
        ↓
Does that user own the directory?
        ↓
Does it have write permission?
        ↓
Can it traverse every parent directory?
        ↓
Is an ACL involved?
        ↓
Is the filesystem read-only?

71. Useful Diagnostic Commands

Current identity

id

File ownership

ls -la

Full path permissions

namei -l /storage/websites/site1.com/public

Ownership

stat /storage/websites/site1.com

ACL

getfacl /storage/websites/site1.com

Processes

ps aux | grep php-fpm

72. A Practical Diagnostic

Suppose:

WordPress
↓
Unable to write uploads

Check:

ls -ld /storage/websites/site1.com/public/wp-content/uploads

Then:

namei -l /storage/websites/site1.com/public/wp-content/uploads

Then determine the PHP-FPM pool’s user.

Finally test:

sudo -u site1 touch /storage/websites/site1.com/public/wp-content/uploads/test.txt

If this fails, the problem is likely filesystem permissions/path access rather than WordPress itself.


73. Don’t Forget SELinux/AppArmor

Linux security can have additional layers such as:

SELinux
AppArmor

Ubuntu commonly uses AppArmor for certain services.

Therefore:

Unix permissions correct

does not always guarantee:

application access allowed

This becomes an advanced troubleshooting topic.


74. Permission Layers

Think of access as:

Application
   ↓
Linux process identity
   ↓
Unix permissions
   ↓
ACL
   ↓
AppArmor/other security policy
   ↓
Filesystem

All relevant controls must permit the operation.


75. The Hosting Security Principle

For every website:

Identity
+
Ownership
+
Permissions
+
Process isolation
+
Database isolation

should agree with each other.

For example:

site1

should consistently map to:

site1 files
site1 PHP-FPM
site1 database
site1 database user

76. Lesson 062 — Core Principle

The most important idea is:

PHP-FPM should execute a site’s PHP as that site’s Linux identity whenever your hosting architecture calls for per-site isolation.

Then:

site1 PHP
   ↓
Linux user site1
   ↓
site1 permissions
   ↓
site1 files

while:

site2 PHP
   ↓
Linux user site2
   ↓
site2 permissions
   ↓
site2 files

This creates the foundation for secure multi-tenant hosting.


Next Lesson — 063

WordPress Installation Automation — From Empty Domain to Working Website

We will now combine the previous lessons into one complete provisioning workflow:

Domain
 ↓
DNS
 ↓
Website directory
 ↓
Linux user
 ↓
Permissions
 ↓
MySQL database
 ↓
Database user
 ↓
PHP-FPM pool
 ↓
Nginx server block
 ↓
WordPress download
 ↓
wp-config.php
 ↓
WordPress installation
 ↓
SSL
 ↓
Final testing

The next lesson will turn these individual concepts into a repeatable command-line installation procedure for any domain on your CresignSys VPS.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *