Apache to Nginx Migration Step by Step

Khimananda Oli 8 min read CI/CD and Automation
Apache to Nginx Migration Step by Step

By Khimananda Oli | Last reviewed: August 2026

Moving a live web server from Apache to Nginx requires translating process-based logic into an event-driven architecture without breaking existing functionality. This Apache to Nginx migration step by step guide provides the exact workflow I use in production environments to ensure configuration parity and zero downtime. Before touching any production configs, review our Nginx vs Apache performance comparison to validate that the architectural trade-offs align with your specific application requirements and traffic patterns.

1. AuditModules & .htaccess2. ParallelNginx on :80803. ValidateDiff Responses4. CutoverSwap Ports / DNS
High-level Apache to Nginx migration step by step workflow ensuring safe transition through parallel validation

How do you prepare for an Apache to Nginx migration step by step?

Preparation prevents the most common migration failures. You cannot simply uninstall Apache and install Nginx; the configuration paradigms are fundamentally different. Apache relies heavily on distributed .htaccess files and per-directory overrides, while Nginx uses centralized server blocks and location directives. Before writing a single line of Nginx config, you must inventory exactly what Apache is doing.

Audit active Apache modules and configurations

Run the following commands to capture your current Apache state. This output becomes your migration checklist:

# List all loaded Apache modules
apachectl -M | sort

# Find all .htaccess files in your web root
find /var/www/html -name ".htaccess" -exec echo "=== {} ===" \; -exec cat {} \;

# Dump current virtual host configurations
apachectl -S

Categorize every module into three buckets: native Nginx equivalent (e.g., mod_rewrite → ngx_http_rewrite_module), requires external tool (e.g., mod_security → ModSecurity-nginx or WAF), or no longer needed. Many legacy Apache modules handled tasks that modern application frameworks now manage internally. Do not migrate obsolete functionality.

Map .htaccess rules to Nginx directives

This is where most migrations stall. Nginx does not read .htaccess files. Every rewrite rule, access restriction, and header manipulation must be translated into the appropriate server or location block. Create a spreadsheet mapping each Apache directive to its Nginx counterpart before touching configuration files. For complex rewrite logic, test translations in isolation using curl against a staging instance before applying them to production configs.

How do you configure Nginx alongside Apache safely?

The safest approach runs both servers simultaneously during the validation phase. Apache continues serving production traffic on port 80/443 while Nginx listens on an alternate port (typically 8080/8443). This parallel setup lets you verify behavior without risking user-facing outages. If you are setting up a fresh environment rather than migrating in place, consult our guide to installing Nginx on Ubuntu for baseline hardening steps.

Install Nginx without disrupting Apache

On Ubuntu/Debian systems, install Nginx but prevent it from starting automatically:

sudo apt update
sudo apt install nginx

# Stop Nginx immediately after install
sudo systemctl stop nginx
sudo systemctl disable nginx

# Verify Apache still owns port 80
sudo ss -tlnp | grep ':80'

Edit /etc/nginx/sites-available/default (or create a new site file) to bind to port 8080 initially. Change every listen 80; to listen 8080; and listen 443 ssl; to listen 8443 ssl;. Start Nginx manually and confirm it binds correctly:

sudo nginx -t
sudo systemctl start nginx
curl -I http://localhost:8080
ClientTest RequestApache :80Production (Baseline)Nginx :8080Validation TargetPHP-FPMunix:/run/php/php8.3-fpm.sockApp Code/var/www/html
Parallel validation architecture: Nginx on port 8080 shares the same PHP-FPM socket and codebase as Apache for accurate response comparison

Configure PHP-FPM integration correctly

Nginx does not embed PHP like Apache's mod_php. It delegates processing to PHP-FPM via FastCGI. Ensure PHP-FPM is installed and running independently of Apache:

sudo apt install php8.3-fpm
sudo systemctl enable --now php8.3-fpm

In your Nginx server block, add the PHP handler within the appropriate location block. Note the use of the Unix socket for lower latency compared to TCP:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

A common mistake is forgetting fastcgi_param SCRIPT_FILENAME. Without it, PHP-FPM receives no file path and returns a blank page or 404. Always test PHP execution explicitly with a simple info.php file before proceeding.

How do you translate Apache rewrite rules and SSL to Nginx?

Rewrite rules and SSL certificates are the two highest-risk translation points. Errors here cause broken links, SEO damage, or security warnings. Treat this phase methodically.

Convert mod_rewrite to Nginx try_files and rewrite

Apache's RewriteRule often handles front-controller routing for frameworks like Laravel or WordPress. In Nginx, try_files is usually cleaner and more performant:

# Apache: RewriteCond %{REQUEST_FILENAME} !-f
#         RewriteCond %{REQUEST_FILENAME} !-d
#         RewriteRule ^(.*)$ index.php?$query_string [L,QSA]

# Nginx equivalent:
location / {
    try_files $uri $uri/ /index.php?$query_string;
}

For redirects (301/302), use Nginx's return directive instead of rewrite when possible. return is processed earlier in the request cycle and avoids regex overhead:

# Permanent redirect
return 301 https://example.com$new_uri;

# Regex rewrite (only when necessary)
rewrite ^/old-path/(.*)$ /new-path/$1 permanent;

Migrate SSL certificates and TLS settings

Copy your existing certificate and key files to Nginx's expected location. Update permissions so only root can read the private key. Configure modern TLS settings that match or exceed your Apache configuration. Refer to our SSL certificate installation guide for Let's Encrypt automation if your certs need renewal during migration.

server {
    listen 8443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/ssl/certs/example.com.pem;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
}

How do you validate and cut over without downtime?

Never switch production traffic based on manual browser checks alone. Automated validation catches edge cases humans miss. Only after parity is confirmed should you perform the final cutover.

Automated response comparison testing

Write a script that sends identical requests to both Apache (port 80) and Nginx (port 8080), then compares status codes, headers, and response bodies. Tools like diff, curl, or dedicated HTTP comparison utilities work well. Pay special attention to:

  • Redirect chains and final destination URLs
  • Cache-Control and security headers
  • Cookie paths and domains
  • Response body content for dynamic pages
  • Error page rendering (404, 500)

If discrepancies exist, fix them in Nginx config and re-test. Do not proceed until automated tests pass for all critical paths.

FeatureApacheNginxArchitectureProcess/Thread per connectionEvent-driven asyncConfig StyleDistributed (.htaccess)Centralized server blocksStatic FilesGoodExcellent (sendfile)Dynamic ContentEmbedded mod_phpExternal PHP-FPMMemory (10k conn)~2-4 GB~50-100 MBReverse ProxyPossible (mod_proxy)Native / Primary Use
Key architectural and operational differences between Apache and Nginx informing migration decisions and configuration translation
Migration Risk AreaCommon Failure ModeValidation MethodRollback Strategy
Rewrite RulesBroken internal links, 404sAutomated URL crawl + status diffRe-enable Apache on port 80
PHP ExecutionBlank pages, wrong SCRIPT_FILENAMETest endpoint returning phpinfo()Verify FPM socket path and perms
SSL/TLSCertificate mismatch, weak ciphersSSL Labs test + curl -v comparisonRestore original cert files
Headers & CookiesSession loss, CORS failuresHeader diff script on auth endpointsAdd missing add_header directives
File Permissions403 Forbidden on uploads/assetsRecursive ls -la comparisonchown/chgrp to www-data

Execute the final cutover

Once validation passes, schedule a maintenance window. The cleanest cutover swaps ports atomically:

  1. Stop Apache: sudo systemctl stop apache2
  2. Update Nginx config to listen on 80/443 (remove 8080/8443)
  3. Test config: sudo nginx -t
  4. Reload Nginx: sudo systemctl reload nginx
  5. Run smoke tests immediately against production URLs
  6. Monitor error logs for 30 minutes: tail -f /var/log/nginx/error.log

Keep Apache installed and configured for at least two weeks post-migration. If a critical issue surfaces that you cannot quickly resolve in Nginx, reverting takes seconds: stop Nginx, start Apache. Only decommission Apache after confirming stability across multiple deployment cycles.

Finalizing Your Apache to Nginx Migration Step by Step

A successful Apache to Nginx migration step by step hinges on disciplined preparation, parallel validation, and atomic cutover—not on speed. The performance and resource efficiency gains are substantial, but only if configuration parity is verified before production traffic touches the new stack. Document every translated directive, maintain rollback capability during the transition period, and integrate Nginx configuration into your infrastructure-as-code pipeline to prevent future drift. If your team needs hands-on support planning or executing this migration for a production workload, reach out to discuss your specific environment.

Frequently Asked Questions

Nginx handles high concurrency better with lower memory usage due to its event-driven architecture. It serves static files faster and acts as a superior reverse proxy for modern PHP-FPM Laravel applications compared to Apache's process-based model.

Nginx does not support .htaccess files. You must manually translate rewrite rules into location blocks within your server configuration file. Use the official nginx converter tool or community scripts to automate basic rewrites, but always validate complex logic manually.

Yes, bind them to different ports like 8080 for Apache and 80 for Nginx. This allows parallel testing before fully switching traffic. Update DNS or load balancer settings only after validating Nginx performance and application compatibility.

Configure fastcgi_pass to point to unix:/run/php/php8.3-fpm.sock instead of TCP localhost:9000 for local setups. Unix sockets reduce overhead and latency. Ensure www-data owns the socket and permissions match your Nginx worker user in 2026 deployments.

Run sudo nginx -t to validate configuration syntax and check for errors. This command prevents service downtime by refusing to apply broken configs. Always test after editing server blocks or upstream definitions during your Apache to Nginx migration step by step.

Yes, mainline Nginx 1.27+ supports HTTP/3 natively. Enable it via listen 443 quic reuseport directives and ensure OpenSSL 3.x or BoringSSL is compiled in. This provides faster TLS handshakes and improved mobile performance over traditional HTTP/2 connections.

Copy existing PEM certificate and key files to /etc/nginx/ssl/. Update ssl_certificate and ssl_certificate_key paths in your server block. Nginx uses the same X.509 format as Apache, so no conversion is needed. Test with curl -vI https://yoursite.com afterward.

Usually PHP-FPM is stopped, misconfigured, or socket permissions are wrong. Check systemctl status php8.3-fpm and verify fastcgi_pass matches the actual socket path. Review /var/log/nginx/error.log for connection refused messages indicating backend unavailability.

Maintain identical URL structures and response codes. Implement exact 301 redirects for any changed paths. Verify canonical tags remain consistent. Monitor Google Search Console crawl stats for two weeks post-migration to catch indexing issues early.

Yes, open-source Nginx is completely free under BSD license for any commercial use. The paid Nginx Plus adds monitoring, active health checks, and F5 support. Most Laravel and DevOps teams successfully run the free version in 2026 production environments.

Use wrk or hey with realistic payloads matching your Laravel app. Test both servers on identical hardware with warm caches. Measure requests per second, p99 latency, and memory consumption under sustained load rather than synthetic hello-world benchmarks.

Add add_header X-Content-Type-Options nosniff, X-Frame-Options DENY, and Strict-Transport-Security max-age=31536000. Hide server version with server_tokens off. These mitigate clickjacking, MIME sniffing, and downgrade attacks without application code changes.

Add gzip on, gzip_types text/css application/javascript application/json image/svg+xml, and gzip_min_length 1000 to your http block. Avoid compressing already-compressed formats like PNG or WebP. Test with curl -H Accept-Encoding:gzip -I to verify Content-Encoding header.

Yes, configure try_files $uri $uri/ /index.php?$query_string in your public location block. Ensure disable_symlinks is set to if_not_owner or off. Verify storage/app/public is properly linked via php artisan storage:link and filesystem permissions allow www-data read access.

Use JSON log_format with fields for request_time, upstream_response_time, status, and remote_addr. Integrate with Vector or Fluent Bit for structured parsing. This enables precise latency analysis and error correlation in Grafana or Datadog dashboards during 2026 observability setups.