Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

How to Roll Back a Failed Deployment Safely

By Kokil Thapa | Last reviewed: August 2026

When a production release breaks critical functionality, knowing how to roll back a failed deployment safely is the difference between a five-minute fix and a four-hour outage. In my experience maintaining Laravel and WordPress systems for Nepal-based legal-tech and eCommerce clients, the ability to revert instantly relies entirely on atomic deployment architecture rather than emergency code patches. This guide covers the exact infrastructure patterns, database strategies, and verification steps required to restore service without data loss.

Many developers treat deployment as a one-way street, focusing exclusively on pushing new code forward. However, a reliable CI/CD pipeline setup must prioritize reversibility as a first-class feature. If your current workflow involves overwriting files directly on the server via FTP or git pull in production, you cannot safely roll back because the previous state no longer exists. Safe rollback requires an architecture where every release is immutable, isolated, and instantly swappable.

How Do You Architect Infrastructure to Roll Back a Failed Deployment Safely?

You cannot safely revert what you have already destroyed. The foundational requirement for safe rollback is an atomic directory structure. On every production server I manage, whether for a high-traffic WooCommerce store or a legal document portal, I enforce a strict three-directory layout:

  • releases/: Contains timestamped directories (e.g., 20260817103000) for each deployment. Each is a complete, self-contained application copy.
  • shared/: Persists across deployments. Stores .env, storage/, user uploads, and session files. Never overwritten during release.
  • current: A symbolic link pointing to the active release directory. This is what Nginx/Apache serves.
Atomic Production Server Structure/releases20260817103000 (Active)20260816091500 (Prev)20260815142000... older releases/shared.envstorage/app/publicstorage/logsPersistent Data/current(Symlink)Points to Active Release
Atomic directory layout enabling instant symlink-based rollback without file restoration

This structure decouples code from state. When you deploy, you create a new timestamped folder, install dependencies, build assets, and link shared resources. Only after all steps succeed do you update the current symlink. If anything fails before that final swap, the live site remains untouched. For Laravel developers in Nepal working with limited server resources, this pattern also prevents disk exhaustion by keeping only the last 3–5 releases and pruning older ones automatically.

Why Direct Git Pull Is Unsafe

Running git pull origin main directly in production mutates the existing directory. If the new code has a fatal error, you have no clean previous state to return to. You would need to git revert, resolve potential merge conflicts, reinstall Composer dependencies, and rebuild assets—all while users see errors. Atomic deploys eliminate this risk entirely by treating each release as an immutable artifact.

What Are the Exact Steps to Execute a Rollback?

When monitoring alerts fire or users report issues, follow this precise sequence. Speed matters, but precision matters more. I have used this exact procedure on Deployer 7-managed Laravel applications serving thousands of daily requests.

  1. Identify the previous stable release. SSH into the server and inspect the releases directory:
    ls -lt /var/www/project/releases/ | head -n 5
    Note the timestamp of the last known-good deployment.
  2. Verify the previous release integrity. Ensure the target directory still exists and contains valid code. Accidental cleanup scripts sometimes delete releases prematurely.
    test -f /var/www/project/releases/20260816091500/artisan && echo "VALID"
  3. Switch the symlink atomically. Use ln -sfn to force-update without race conditions:
    ln -sfn /var/www/project/releases/20260816091500 /var/www/project/current
    This command is instantaneous and atomic at the filesystem level.
  4. Reload PHP-FPM. OPcache may still serve stale bytecode from the failed release. Force a graceful reload:
    sudo systemctl reload php8.4-fpm
    Adjust the version number to match your installed PHP (8.2, 8.3, or 8.4).
  5. Clear application caches. Even though code reverted, cached config or routes might reference new classes. Run:
    cd /var/www/project/current && php artisan optimize:clear
  6. Verify health endpoints. Hit your application’s health check URL and confirm response codes, database connectivity, and key business flows.

If you use Deployer 7, this entire process collapses into a single command:

dep rollback production

Deployer handles symlink switching, PHP-FPM reloading, and cache clearing based on your recipe configuration. I strongly recommend wrapping this in your CI/CD pipeline as an emergency job triggered manually when automated tests fail post-deploy.

How Do You Handle Database Migrations During Rollback?

Code rollback is straightforward; database changes are where most teams get burned. Backward-incompatible migrations are the primary reason rollbacks fail. If your new release added a non-nullable column without a default, and you rolled back code that doesn’t know about that column, your old code will crash just as hard as the new code did.

Database Migration Rollback Decision TreeMigration Applied?YesIs Migration Backward-Compatible?(Additive only? No dropped columns?)YESSafe to Roll Back CodeLeave migration applied.Old code ignores new columns.NORollback Requires DB RevertRun down() migration ORrestore from pre-deploy backupNoSafe to Roll Back⚠ Golden RuleNever drop columns or rename tables in forward migrations.
Decision framework for determining whether database state blocks safe code rollback

The Expand-and-Contract Pattern

To make rollbacks safe, adopt the expand-and-contract pattern for schema changes:

  • Expand phase: Add new columns as nullable or with defaults. Deploy code that writes to both old and new columns.
  • Migrate data: Backfill existing rows via background job or artisan command.
  • Contract phase (future release): Once confident, remove old column references and drop the legacy column in a separate deployment.

This ensures any intermediate release can be rolled back without breaking. On a recent eCommerce project, we needed to split a single address field into structured components. We added the new fields as nullable, updated the checkout form to populate both, ran a backfill script, and only removed the old field three releases later. Had we needed to roll back during the transition, the old code would have continued working with the original column.

Pre-Deployment Database Snapshots

For migrations that cannot be made backward-compatible (rare but possible), take an automated snapshot before deploying:

mysqldump --single-transaction --routines --triggers \
  -u root -p"$DB_PASS" production_db > /backups/pre-deploy-$(date +%Y%m%d%H%M%S).sql

Store backups outside the release directory. If rollback requires database restoration, having a verified snapshot reduces recovery time from hours to minutes. For PostgreSQL, use pg_dump --format=custom for faster parallel restores.

How Do Automated Pipelines Prevent Failed Deployments?

Manual rollbacks are emergency measures; prevention is the real goal. Your CI/CD pipeline should catch failures before they reach production. Here is the validation gate I implement for every Laravel application:

StageCheckFailure Action
BuildComposer install + npm ci + asset compilationHalt pipeline, notify team
TestPHPUnit/Pest suite + static analysis (PHPStan)Halt pipeline, block deploy
StagingDeploy to staging, run smoke tests against real DB copyAuto-rollback staging, alert developer
ProductionAtomic deploy + health check within 60 secondsAuto-trigger dep rollback if health check fails

The critical piece is the post-deploy health check. Configure your pipeline to hit a dedicated /health endpoint that verifies database connectivity, cache availability, and queue responsiveness. If the endpoint returns non-200 within the timeout window, automatically execute rollback. This removes human reaction time from the equation.

Cache and Queue Considerations

After rollback, stale cached data can cause phantom errors. Always clear OPcache, application cache, and route cache. For queue workers, restart them to pick up reverted job classes:

sudo systemctl restart php8.4-fpm
php artisan queue:restart
php artisan optimize:clear

On systems using Redis for caching, consider versioning cache keys by release timestamp. This avoids cross-release contamination entirely, though it adds complexity. For most Nepal-based projects I maintain, simple cache clearing post-rollback suffices.

CI/CD Pipeline with Auto-RollbackBuild & TestPHPUnit + PHPStanDeploy StagingSmoke TestsDeploy ProdAtomic SymlinkHealth OK?YESRelease LiveNOAUTO ROLLBACKdep rollback + notify SlackDeveloper notified to investigate
CI/CD pipeline flow showing automatic rollback trigger when production health checks fail

How Do You Verify System Integrity After Rollback?

Reverting code does not guarantee system health. Post-rollback verification must be systematic, not hopeful. Create a runbook covering these checks:

  1. Syntactic validation: Confirm PHP-FPM is running without errors (systemctl status php8.4-fpm). Check Laravel logs for fatal exceptions immediately after reload.
  2. Business-critical flows: Test actual user journeys, not just homepage loads. For a legal-tech portal, verify document upload and payment processing. For eCommerce, test cart-to-checkout flow.
  3. Queue health: Ensure queued jobs are processing. Stale job classes from the failed release may still be in the queue. Monitor php artisan queue:work output for class-not-found errors.
  4. External integrations: Verify API connections to payment gateways (eSewa, Khalti, Stripe), SMS providers, and third-party services. Configuration drift between releases can break credentials.
  5. Performance baselines: Compare response times and error rates against pre-deployment metrics. A successful rollback should restore previous performance characteristics within minutes.

Document every rollback incident. Note the root cause, time-to-recovery, and preventive measures. This builds institutional knowledge and prevents recurrence. On projects where I serve as the DevOps engineer, we maintain a shared incident log that directly informs future pipeline improvements.

Conclusion

Understanding how to roll back a failed deployment safely transforms deployment from a source of anxiety into a routine operation. The foundation is atomic infrastructure with immutable releases and persistent shared state. Database migrations must be designed for reversibility using expand-and-contract patterns. Automated pipelines should validate aggressively and trigger rollback without human intervention when health checks fail. Post-rollback verification completes the cycle, ensuring restored functionality matches expectations.

If your current deployment process lacks these safeguards, start by implementing atomic directory structures and symlink switching. Even without full CI/CD automation, this single change eliminates the most dangerous failure modes. For teams managing production Laravel, WordPress, or custom PHP applications, investing in rollback capability pays dividends every time a release goes wrong—which, in production systems, is inevitable.

Need help architecting a resilient deployment pipeline or recovering from a problematic release? Contact me to discuss your infrastructure needs.

Frequently Asked Questions

Run dep rollback in your project root. This instantly symlinks the previous release directory and reloads PHP-FPM, restoring the site within seconds without rebuilding assets or reinstalling dependencies.

Keep three to five releases. This provides sufficient history for emergency reverts while preventing disk exhaustion on production servers. Configure this via keep_releases in your deploy.php file.

No. Rollbacks only revert application code and static assets. Database migrations are not automatically reversed. You must manually run migrate:rollback or restore from backup if schema changes caused the failure.

Common causes include missing previous release directories, broken symlinks, or incorrect file permissions after a partial deploy. Check that releases/ contains valid prior versions and that the current symlink target exists. Verify ownership matches your deploy user and that PHP-FPM has read access to the release path. On shared EC2 infrastructure I maintain, permission drift after manual interventions is the most frequent culprit.

Hit critical endpoints immediately after rollback: health check, login page, and one transactional route. Check Laravel logs for fresh errors, confirm PHP-FPM reloaded via systemctl status php8.3-fpm, and validate the current symlink points to the expected release timestamp. I always run a smoke test script post-rollback on client projects rather than relying solely on HTTP 200 responses, since cached pages can mask underlying failures.

Yes, but code rollback alone leaves the database in a mismatched state. If the new migration added columns the old code doesn't expect, the reverted app may still error. Either write reversible migrations with proper down methods or restore the database from a pre-deploy snapshot. In production Laravel applications I manage, I schedule mysqldump before every deploy specifically for this scenario. Never assume backward compatibility between schema versions.

Running jobs continue executing against the old code until completion, which can cause failures if they reference classes removed in the failed release. New jobs queued after rollback use restored code. Always pause queue workers before deploying using php artisan queue:pause, and resume only after confirming the rollback succeeded. On high-traffic eCommerce sites, I drain queues completely before any deploy to avoid orphaned job processing against inconsistent codebases.

Reload PHP-FPM immediately after switching symlinks. Deployer 7 handles this automatically via the deploy:fpm task, but verify your configuration targets the correct service name like php8.3-fpm. If using Nginx with fastcgi_cache, purge that separately. Stale opcache is the most common reason rollbacks appear to fail despite correct symlinks. I've debugged this repeatedly on Ubuntu 22/24 servers where the FPM service name didn't match the deploy script assumption.

Keep rollbacks manual. Automated rollback triggers risk cascading failures when health checks produce false positives due to transient issues like third-party API timeouts. Instead, configure GitLab CI to notify on deploy failure so a human decides. On sister sites sharing Deployer 7 pipelines, I trigger alerts via webhook but never auto-revert. The thirty seconds spent confirming the actual failure mode prevents unnecessary rollbacks that mask real problems.

Shared .env files persist across releases in Deployer's shared directory, so rollbacks don't revert config changes. If the failed deploy introduced new env vars the old code doesn't support, you must manually edit .env after rollback. Document all env changes in deploy commit messages. For legal-tech portals handling sensitive credentials, I version env changes separately and maintain a changelog to ensure rollback includes necessary config adjustments.

dep rollback restores the exact previously deployed artifact including built frontend assets and vendor dependencies, taking seconds. git revert creates a new commit requiring full rebuild, composer install, and asset compilation, taking minutes. Use dep rollback for immediate recovery. Reserve git revert for permanently undoing changes in version control after the incident resolves. In production emergencies, speed matters more than git history cleanliness.

Maintain a staging environment mirroring production's Deployer configuration and release structure. Intentionally deploy broken code monthly and practice rollback timing. Verify shared directories, permissions, and FPM reload behavior match production exactly. Document actual rollback duration and any deviations from expected behavior. On client projects, I include rollback drills in maintenance contracts because untested rollback procedures fail precisely when needed most. Budget Rs 15,000–25,000 (~USD 110–185) annually for this validation work.

Local environments rarely replicate production's permission model, PHP-FPM pooling, or multi-version PHP setups. Common mismatches include deploy user lacking write access to releases/, wrong PHP binary in PATH during rollback, or systemd service names differing between Ubuntu versions. Audit production with ls -la releases/current and php -v under the deploy user account. I've resolved this on shared EC2 instances where the cron user differed from the deploy user, causing silent permission failures.

Provide three concrete data points: timestamp of rollback completion, specific symptom resolved, and verification method used. Avoid technical jargon like symlink or opcache. Example: Site restored at 2:47 PM. Checkout functionality confirmed working via test transaction. Monitoring shows normal error rates. Follow up with root cause analysis within 24 hours. Business owners need confidence in resolution, not implementation details. This approach has prevented escalation on multiple eCommerce incidents.

Set thresholds for error rate exceeding 5% over two minutes, p95 latency doubling baseline, or payment gateway failure rate above 1%. Combine automated alerts with manual verification before rolling back. False positives from CDN cache misses or third-party outages waste recovery time. On WooCommerce stores, I monitor cart abandonment spikes as leading indicators. Configure Grafana or Sentry alerts to Slack with direct links to dashboards, enabling informed decisions within sixty seconds rather than reactive panic.

Share this article

Quick Contact Options
Choose how you want to connect me: