Migrating from nginx to bext
This guide walks through replacing nginx with bext — from testing your config to a full production cutover.
Step 1: Test Your Config
Before making any changes, verify bext can parse your nginx config:
# Auto-detect and validate
bext nginx check
# Or specify a path
bext nginx check --nginx-config /etc/nginx/nginx.conf
bext will report:
- Number of virtual hosts detected
- Number of upstream blocks
- SSL certificate paths
- Any unsupported directives (with file:line locations)
Step 2: Run Side-by-Side
Start bext on a different port to test alongside your running nginx:
# Run bext on port 8080, reading your nginx config
bext serve --nginx-config /etc/nginx/nginx.conf --port 8080
Test individual endpoints:
# Compare responses
curl -H "Host: example.com" http://localhost:80/api/health # nginx
curl -H "Host: example.com" http://localhost:8080/api/health # bext
Step 3: Use the Preflight Dashboard
For a visual overview of everything bext detected:
sudo bext nginx-takeover --port 9999
Open http://localhost:9999 in your browser. The dashboard shows:
- All virtual hosts with their server names, ports, and locations
- SSL certificates and their expiration dates
- Upstream backends and load balancing strategies
- Conversion warnings for any unsupported directives
- Eight preflight checks (config parsed, SSL certs accessible, ports available, etc.)
Step 4: Choose Your Migration Path
Option A: Quick Switch (Masquerade Mode)
Replace nginx entirely with a single command:
sudo bext nginx masquerade install
This:
1. Backs up your real nginx binary to /usr/sbin/nginx.real
2. Installs a bext shim at /usr/sbin/nginx
3. Creates a systemd drop-in that runs bext instead of nginx
4. Runs systemctl daemon-reload
After installation, systemctl restart nginx starts bext — and all existing tools (certbot, logrotate, monitoring) continue to work unchanged.
To undo:
sudo bext nginx masquerade uninstall
Option B: Atomic Takeover
Use the takeover process for a controlled, reversible migration:
sudo bext nginx-takeover
The Takeover Process stops nginx, binds bext to ports 80/443, and provides a rollback button if anything goes wrong.
Option C: Gradual Migration
Run bext behind nginx as a reverse proxy first, then migrate services one at a time:
# nginx proxying to bext for specific paths
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
Once you've validated bext handles your traffic correctly, switch to Option A or B.
Config Mapping Quick Reference
| nginx | bext equivalent |
|---|---|
nginx.conf |
Parsed automatically — no bext config needed |
sites-available/ |
Detected via include directives |
upstream {} |
Converted to bext upstream pools |
proxy_cache_path |
Mapped to bext's tiered cache |
ssl_certificate |
Loaded into SNI resolver |
limit_req_zone |
Converted to token bucket rate limiter |
auth_basic_user_file |
htpasswd loaded with bcrypt support |
SIGHUP reload |
Supported — config + TLS certs hot-reloaded |
SIGUSR1 log reopen |
Supported — logrotate-compatible |
/run/nginx.pid |
Written and managed by bext |
Common Patterns
HTTPS Redirect
nginx:
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
bext handles this identically — the return directive is converted and executed at the server level before location matching.
PHP/Laravel
nginx:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
bext proxies PHP requests through FastCGI to your existing PHP-FPM pools. For worker mode performance (15K req/s), see the Laravel integration.
SPA with API Backend
nginx:
location /api/ {
proxy_pass http://backend;
}
location / {
try_files $uri $uri/ /index.html;
}
Both the proxy and try_files are fully supported, including the SPA fallback pattern.
WebSocket Proxy
nginx:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
bext auto-detects WebSocket upgrade headers and handles the connection upgrade.
WordPress / PHP-FPM Sites
bext automatically detects WordPress sites and routes PHP requests through FastCGI to your existing PHP-FPM sockets. There is no embedded PHP runtime to install or configure — bext acts as a FastCGI proxy to the PHP-FPM pools already running on your server.
How Detection Works
When bext parses your nginx config, it identifies WordPress sites by checking for:
- A wp-config.php file in the document root
- A wp-content/ directory in the document root
If either is found, the site is flagged as a WordPress site and routing is configured accordingly.
PHP Version Detection
bext extracts the PHP version from the FPM socket name. For example:
- unix:/run/php/php8.4-fpm.sock → PHP 8.4
- unix:/run/php/php8.1-fpm.sock → PHP 8.1
This is used for reporting in bext nginx list and cloud sync metadata.
Static Asset Serving
Static assets under wp-content/ (CSS, JS, images, fonts) are served directly by bext without passing through PHP. This matches the typical nginx location ~* \.(css|js|png|jpg|...)$ pattern and avoids unnecessary PHP overhead.
WordPress Routing
All standard WordPress routes work correctly:
- wp-admin/ — admin dashboard
- wp-login.php — login page
- xmlrpc.php — XML-RPC API
- All PHP files — routed through FastCGI to PHP-FPM
- try_files $uri /index.php — pretty permalinks are handled; requests that don't match a static file fall through to index.php with the original URI
No changes to your WordPress installation or PHP-FPM configuration are needed.
After Migration
Once bext is serving your traffic:
- SIGHUP reloads config and TLS certs (certbot integration works unchanged)
- SIGUSR1 reopens log files (logrotate works unchanged)
- PID file at /run/nginx.pid (monitoring tools work unchanged)
- nginx -t tests config syntax (via the bext shim)
- nginx -s reload sends SIGHUP (via the bext shim)
Your existing operational tooling should work without modification.
Cloud Sync
After migration, register all your sites in the bext cloud dashboard with a single command. The cloud dashboard at cloud.bext.dev shows all sites grouped by client, with their domains, framework types, and SSL status.
Authentication
First, authenticate with the cloud:
bext login
This stores your API key locally. Alternatively, set BEXT_CLOUD_API_KEY on both the CLI machine and the cloud server.
Syncing Sites
# 1. See all detected vhosts with stable IDs
bext nginx list
# 2. Compare local nginx config vs cloud state
bext nginx diff
# 3. Sync everything to the cloud
bext nginx sync-cloud
# 4. Or sync specific vhosts only
bext nginx sync-cloud --only a282,5427
# 5. Preview first with --dry-run
bext nginx sync-cloud --dry-run
What Gets Synced
The sync pushes the following metadata for each vhost:
- Project name — derived from the primary domain
- Domains — all server_name entries
- Framework type — static, proxy/hono, PHP/laravel, PHP/wordpress, etc.
- Root paths — document root directories
- PHP-FPM sockets — socket paths and detected PHP version
- SSL certificate expiry — parsed from the certificate files
The sync detects each vhost's framework and creates a project with all its domains. Existing projects are skipped — new domains are still added.
Diffing and Cleanup
Use bext nginx diff to compare local config vs cloud state. Each vhost is classified as synced, new, drift (domain mismatch), or stale (in cloud but not in nginx).
To clean up projects that no longer have a matching vhost in nginx:
bext nginx remove-cloud --only c02c
The cloud URL defaults to http://localhost:3025 (override with --cloud-url or BEXT_CLOUD_URL).
Rollback
If you need to revert to the original nginx, the process depends on which migration path you used.
Masquerade Mode Rollback
If you used masquerade mode (Option A), uninstall the shim and restart nginx:
# Restore the real nginx binary from /usr/sbin/nginx.real
sudo bext nginx masquerade uninstall
# Start the original nginx
sudo systemctl restart nginx
This removes the bext shim from /usr/sbin/nginx, restores the original binary, removes the systemd drop-in, and runs systemctl daemon-reload.
Migration Script Rollback
If a rollback script was generated during migration, you can use it directly:
sudo ./rollback.sh
The script performs the same steps: restores the original binary, removes systemd overrides, and restarts nginx.
Takeover Mode Rollback
If you used the takeover process (Option B), the preflight dashboard includes a rollback button. Alternatively, stop bext and start nginx:
sudo systemctl stop bext
sudo systemctl start nginx
Verifying Rollback
After rollback, verify nginx is running:
nginx -v # Should show the real nginx version
systemctl status nginx # Should show active (running)
curl -I http://localhost # Should return nginx server header