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