When configuring Nginx to serve websites from user home directories (/home/username/public_html
), the "13: Permission denied" error typically occurs because the Nginx worker process (running as user nginx
) doesn't have proper access permissions to the user's home directory structure.
On CentOS/RHEL systems, user home directories typically have 700 permissions by default:
drwx------. 5 website website 4096 Jan 2 19:45 /home/website
This means only the owner (website
) can access the directory, while Nginx runs as user nginx
.
Here's how to properly configure this setup:
# Set correct permissions for the home directory
sudo chmod 711 /home/website
# Set permissions for the public_html directory
sudo chmod 755 /home/website/public_html
# Ensure files are readable by Nginx
sudo chown -R website:nginx /home/website/public_html
find /home/website/public_html -type f -exec chmod 644 {} \;
find /home/website/public_html -type d -exec chmod 755 {} \;
Even with getenforce
showing "Disabled", you might need to set proper contexts if SELinux was previously enabled:
# Check current SELinux context
ls -Z /home/website/public_html
# Set proper context if needed
sudo chcon -R -t httpd_sys_content_t /home/website/public_html
Your server block looks good, but we can enhance it for better security:
server {
listen 80;
listen [::]:80;
server_name website.com www.website.com;
root /home/website/public_html;
index index.html index.htm index.php;
access_log /var/log/nginx/website.access.log;
error_log /var/log/nginx/website.error.log;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php-fpm/website.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param PHP_VALUE "open_basedir=/home/website/public_html:/tmp";
}
location ~ /\.ht {
deny all;
}
}
Create a dedicated PHP-FPM pool for better isolation:
[website]
user = website
group = nginx
listen = /var/run/php-fpm/website.sock
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
php_admin_value[open_basedir] = /home/website/public_html:/tmp
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_value[upload_tmp_dir] = /home/website/tmp
After making these changes:
# Test Nginx configuration
sudo nginx -t
# Restart services
sudo systemctl restart nginx php-fpm
# Verify process ownership
ps aux | grep nginx
ps aux | grep php-fpm
# Check directory permissions
namei -l /home/website/public_html/index.html
The key to solving Nginx permission issues lies in understanding Linux's file permission system. When Nginx tries to access /home/website/public_html
, it needs both:
execute (x) permission on /home
execute (x) permission on /home/website
read (r) permission on public_html/
First, verify the current permissions structure:
ls -ld /home
ls -ld /home/website
ls -ld /home/website/public_html
Typical output showing the problem might look like:
drwx------ 5 website website 4096 Jan 2 19:45 /home/website
The most secure solution involves setting correct permissions while maintaining security:
sudo chmod 711 /home
sudo chmod 711 /home/website
sudo chown -R website:nginx /home/website/public_html
sudo chmod 750 /home/website/public_html
Even with SELinux disabled, it's good practice to know the proper contexts:
sudo chcon -R -t httpd_sys_content_t /home/website/public_html
Edit /etc/nginx/nginx.conf
to ensure the worker process runs with proper permissions:
user nginx;
worker_processes auto;
For PHP processing, ensure your PHP-FPM pool matches:
[website]
user = website
group = nginx
listen.owner = nginx
listen.group = nginx
After making changes, always test:
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl restart php-fpm
For more security, consider symlinking to avoid home directory access:
sudo mkdir -p /var/www/website.com
sudo ln -s /home/website/public_html /var/www/website.com/html
Then update your Nginx config to point to /var/www/website.com/html