Yeah, preparing your dev machine after switching OS is always annoying. Dealing with permissions, configs, and just trying to print hello world can feel like a nightmare.
So let’s tackle this once and for all. This walkthrough is on Arch, but the same logic applies to other distros too.
Step 1: Install OpenResty (or Nginx / Apache)
You can go with nginx, apache, or openresty. I prefer OpenResty because it ships with Lua support — which means you can do cool stuff before the request even hits your backend.
On Arch, OpenResty isn’t in the official repos. You’ll need to grab it from the AUR using yay (or build it yourself with makepkg).
After installation, note that OpenResty’s config folder lives under /opt
, which is different from what the official docs say.
Even if you never touch Lua, you still get a perfectly good nginx for local development.
Step 2: Create a Dedicated User for OpenResty
We don’t want to run everything as root. Let’s create a dedicated system user like this:
sudo useradd -r -s /usr/bin/nologin -d /var/lib/openresty -M openresty
sudo mkdir -p /var/lib/openresty
sudo chown openresty:openresty /var/lib/openresty
Now we’ll run OpenResty as this new openresty user.
Step 3: Install PHP + PHP-FPM
Install PHP and PHP-FPM. Then create a custom pool for development. Here’s a battle-tested config:
[dev]
; Run PHP-FPM as the current user
user = <current user>
group = <current user>
; Socket for OpenResty to connect
listen = /run/php-fpm/dev.sock
listen.owner = <current user>
listen.group = openresty
listen.mode = 0660
; Process management
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
; Logging
access.log = /path/to/log/php-access.log
php_admin_value[error_log] = /path/to/logs/php-error.log
php_admin_flag[log_errors] = on
; Dev-friendly PHP settings
php_admin_value[display_errors] = 1
php_admin_value[display_startup_errors] = 1
php_admin_value[error_reporting] = E_ALL
php_admin_value[memory_limit] = 1024M
php_admin_value[max_execution_time] = 0
Why this works:
- The pool runs as your user → so PHP has the same file permissions you do.
- The socket belongs to your user + group
openresty
→ so both PHP and OpenResty can talk safely. - Cleaner permissions, less pain.
also if you ask me just rename the default pool file extension to disable it.
Step 4: Configure OpenResty (Nginx)
Back up the default config and replace it with something like this:
user openresty;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
access_log off;
error_log stderr warn;
upstream php-fpm {
server unix:/run/php-fpm/dev.sock;
}
server {
listen 80;
server_name localhost;
root /path/to/public_html;
index index.php index.html;
#block for phpmyadmin v6 if you are going to use it
#location ^~ /pma {
# alias /path/to/public_html/pma/public;
# index index.php;
# try_files $uri $uri/ /index.php?$args;
#nested php handler.its important if you use alias
# location ~ \.php$ {
# include fastcgi_params;
# fastcgi_pass php-fpm;
# fastcgi_param SCRIPT_FILENAME $request_filename;
# }
#}
#you can do the same for other projects that uses mvc with public folder
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php-fpm;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ /\.(ht|git|env) {
deny all;
}
}
}
Now we use ACL for permissions because using chmod
for every new file is inefficient.
1. Make sure ACL is installed and enabled on the partition containing the public_html
folder.
2. Give openresty
execute permission on each parent folder so it can traverse the path:
sudo setfacl -m u:openresty:x /path/to/public_html_parent
You must do this for each parent folder. For example, if your path is /var/www/html/nginx/user/public_html
:
sudo setfacl -m u:openresty:x /var
sudo setfacl -m u:openresty:x /var/www
sudo setfacl -m u:openresty:x /var/www/html
sudo setfacl -m u:openresty:x /var/www/html/nginx
sudo setfacl -m u:openresty:x /var/www/html/nginx/user
3.Give openresty
read and execute permission on public_html
itself:
sudo setfacl -k /path/to/public_html
sudo setfacl -d -m u:openresty:rx /path/to/public_html
#for existing folders:
sudo find /path/to/public_html -type d -exec sudo setfacl -m u:openresty:rx {} \;
#for existing files:
sudo find /path/to/public_html -type f -exec sudo setfacl -m u:openresty:r {} \;
- Parent folders must have execute permission so OpenResty can traverse them.
- ACL ensures that new files and folders inherit correct permissions automatically.
Now if you are lazy AS F(me:D) you can do the ACL part with one line command:
PUBLIC_HTML="/var/www/html/nginx/user/public_html"; sudo setfacl -k "$PUBLIC_HTML"; if command -v findmnt >/dev/null 2>&1; then MP=$(findmnt -n -o TARGET --target "$PUBLIC_HTML" 2>/dev/null); else MP=$(df --output=target "$PUBLIC_HTML" 2>/dev/null | tail -1); fi; P=$(dirname "$PUBLIC_HTML"); while [ "$P" != "$MP" ] && [ "$P" != "/" ]; do [ -d "$P" ] && sudo setfacl -m u:openresty:x "$P"; P=$(dirname "$P"); done; if [ "$P" = "$MP" ] && [ "$MP" != "/" ]; then [ -d "$P" ] && sudo setfacl -m u:openresty:x "$P"; fi; sudo setfacl -d -m u:openresty:rx "$PUBLIC_HTML"; sudo find "$PUBLIC_HTML" -type d -exec sudo setfacl -m u:openresty:rx {} \;; sudo find "$PUBLIC_HTML" -type f -exec sudo setfacl -m u:openresty:r {} \;
Common issues & fixes
- PHP-FPM socket permission denied:
Make surelisten.owner
andlisten.group
in the pool match your OpenResty user and your current user. - Cannot start OpenResty:
Check that/var/lib/openresty
is owned byopenresty
and that the config syntax is valid:
sudo openresty -t
- Port 80 in use:
Kill the process using it:
sudo lsof -i :80
sudo kill <pid>
Done 🎉
And that’s it. Now you’ve got a professional local PHP dev environment with:
- Correct permissions
- Clean separation of users
- OpenResty power (with Lua, if you ever want it)
No more soul-eating permission battles. Just code and go 🚀.
Leave a Reply