coderepo

Setting Up a Clean PHP + OpenResty Dev Environment on Arch Linux

Logged In

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 sure listen.owner and listen.group in the pool match your OpenResty user and your current user.
  • Cannot start OpenResty:
    Check that /var/lib/openresty is owned by openresty 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 🚀.

Comments

Leave a Reply

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