Over the years of working with WordPress and PHP projects I accumulated a large number of local sites. Creating nginx vhosts, generating SSL certificates, editing /etc/hosts, and preparing project directories manually quickly becomes repetitive and annoying.
So I built a simple idea:
one command → fully ready local project
No Docker. No compose. No extra layers.
Just native Linux tooling.
Core idea
The architecture is based on a few simple principles:
- one wildcard SSL certificate
- one nginx vhost
- dynamic document root via
$host - automatic project bootstrap
- automatic hosts management
Result:
mkcert-wildcard site.test
Creates:
~/www/site.test/public
/etc/hosts entry
SSL ready
nginx ready
php ready
Site works instantly.
nginx structure
My nginx layout:
/etc/nginx/
├── sites-available
│ ├── default.conf
│ ├── pma.test.conf
│ └── wildcard.test.conf
├── sites-enabled
├── ssl
│ ├── wildcard.test.pem
│ └── wildcard.test-key.pem
Classic layout:
sites-available → configs
sites-enabled → symlinks
Simple and predictable.
nginx main config
Minimal nginx.conf:
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
http2 on;
server_name _;
root /home/sergey/www/$host/public;
index index.php index.html;
ssl_certificate /etc/nginx/ssl/wildcard.test.pem;
ssl_certificate_key /etc/nginx/ssl/wildcard.test-key.pem;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/sergey.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~* \.(jpg|jpeg|png|gif|css|js|svg|woff|woff2|webp)$ {
expires max;
log_not_found off;
}
}
Nothing fancy. Just clean nginx.
The key part — wildcard vhost
wildcard.test.conf:
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
http2 on;
server_name _;
root /home/sergey/www/$host/public;
index index.php index.html;
ssl_certificate /etc/nginx/ssl/wildcard.test.pem;
ssl_certificate_key /etc/nginx/ssl/wildcard.test-key.pem;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/sergey.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~* \.(jpg|jpeg|png|gif|css|js|svg|woff|woff2|webp)$ {
expires max;
log_not_found off;
}
}
The important idea is:
root /home/sergey/www/$host/public;
nginx dynamically resolves project directory based on domain.
No more vhosts needed.
PHP-FPM setup
I use a dedicated pool:
/etc/php/php-fpm.d/sergey.conf
With a unix socket:
/run/php-fpm/sergey.sock
Unix sockets are faster and cleaner for local environments.
SSL with mkcert
One certificate for all local domains:
mkcert localhost \
127.0.0.1 ::1 \
wordpress.test \
project.test \
client.test
Used across all projects.
No per-site SSL setup required.
Automation via fish
The real productivity gain comes from automation:
function mkcert-wildcard
set ssl_dir /etc/nginx/ssl
set cert_name wildcard.test.pem
set key_name wildcard.test-key.pem
mkcert $argv
set cert (ls -t *.pem | grep -v key | head -n 1)
set key (ls -t *-key.pem | head -n 1)
sudo cp $cert $ssl_dir/$cert_name
sudo cp $key $ssl_dir/$key_name
sudo chmod 644 $ssl_dir/$cert_name
sudo chmod 600 $ssl_dir/$key_name
rm -f $cert $key
for domain in $argv
if string match -rq '^(127\.|::1|localhost$)' $domain
continue
end
if not grep -qw "$domain" /etc/hosts
echo "127.0.0.1 $domain" | sudo tee -a /etc/hosts > /dev/null
end
set project_dir ~/www/$domain
if not test -d $project_dir
mkdir -p $project_dir/public
touch $project_dir/public/index.php
end
end
sudo systemctl restart nginx
sudo systemctl restart php-fpm
end
Now creating a new site is trivial:
mkcert-wildcard newproject.test
Project ready in seconds.
Why not Docker?
Docker makes sense when you need:
- multiple PHP versions
- multiple DB versions
- exact production replication
- team onboarding environments
But for daily PHP/WordPress development:
native stack often wins:
- faster
- simpler
- easier debugging
- fewer moving parts
- no container overhead
Results
This setup provides:
- zero config projects
- zero vhosts
- zero SSL setup
- instant project bootstrap
- production-like environment
And most importantly:
one command → working site
Final thoughts
If you maintain many local projects, this approach saves a significant amount of time and removes friction from daily development.
Sometimes the simplest native solution is still the most productive one.
Leave a Reply