Quick Answer: A four-hour fiber cut in Junagadh hit in the middle of a client deploy in 2026. I kept the release moving over a 4G hotspot, capped the blast radius with a rollback ledger, and finished with zero rows lost. The fix was not heroics; it was a failover lane, a 42 MB artifact, and a rule that every migration step gets a written reverse.
Key Takeaways
- The deploy did not fail because the internet died; it failed because the runbook assumed it would not: the first 20 minutes went to checking the ISP instead of switching to the backup link.
- 4G is asymmetric: I had 12 Mbps down and 1.8 Mbps up. That is fine for SSH and terrible for pushing a 42 MB artifact. Stage the artifact before the outage window.
- A rollback ledger beats memory: every migration, queue restart, and cache clear got a line with a timestamp, a hash, and a reverse step. During the incident, the ledger was the only reliable timeline.
- Zero data loss came from pausing writes, not from a clever database trick: the app entered read-only mode for 38 minutes while the migration ran. Boring beats clever at 15:00 on a Tuesday.
13:10 — The Deploy Started
The client runs a Laravel 12 app on a 2-vCPU VPS in Mumbai: nginx, PHP-FPM, MySQL 8, Redis, and a queue worker for invoice emails. The release added a gst_returns table, a backfill job, and a new invoice status. It was a medium-risk deploy: one migration, two new indexes, no destructive column changes.
I follow the same sequence every time. Put the app in maintenance mode with a bypass token, take a database snapshot, run migrations, clear config and route caches, restart the queue, run five smoke checks, then lift maintenance mode. The whole thing usually takes 18 minutes.
At 13:10 the snapshot finished and the migration started. At 13:14 the first index build was at 62%. At 13:18 the SSH session froze.
13:22 — The Fiber Cut
I checked the obvious things first: the VPS status page, a second SSH session, and a ping to 1.1.1.1. The VPS was up. My local link was down. The router showed a red WAN light.
A call to the ISP confirmed a fiber cut near the Junagadh bypass. The estimate was "two to four hours". I had a migration running on the server, a queue worker stopped, and no way to see the result.
The first mistake was spending 18 minutes on the ISP call and router reboots before switching to the phone. The backup link was already in my bag. I knew the runbook said "switch first, diagnose second". I diagnosed first because the deploy felt urgent. That is exactly when the runbook matters.
13:40 — 4G Failover
I tethered a phone over USB, set it as the default route, and kept the fiber interface up but with a higher metric so it would take over when it returned. The commands are boring, which is the point:
# List connections and find the USB tether interface
nmcli connection show
# Give the phone route a higher metric (backup)
nmcli connection modify "USB tether" ipv4.route-metric 600
nmcli connection modify "Wired connection 1" ipv4.route-metric 100
# Bring the tether up without dropping the fiber config
nmcli connection up "USB tether"
# Confirm which default route wins
ip route show default
# default via 192.168.1.1 dev eth0 metric 100
# default via 192.168.42.129 dev usb0 metric 600
The fiber route stayed in the table, so when the ISP restored the link, traffic moved back without a manual step. That detail saved a second outage at 16:00.
The 4G link measured 12 Mbps down and 1.8 Mbps up on a speed test. SSH was usable. git push was not. A 42 MB release artifact would have taken about three minutes on a good day and closer to four on that uplink, with a real chance of timing out.
I did not push from the laptop. I used the artifact that was already staged on the server from the previous release, applied the migration diff by hand, and uploaded only the changed files over rsync --partial. The upload was 6.4 MB. It took 51 seconds.
14:05 — The Migration Was Still Running
The migration had not failed; it was waiting on a metadata lock. The gst_returns table creation was blocked by a long-running reporting query that started at 12:58. On fiber I would have seen the lock in the first minute. On a frozen SSH session, I saw it 43 minutes late.
I killed the reporting query after checking with the client's analyst. The migration finished at 14:12. The index build took another 11 minutes. During that window the app was in maintenance mode with a bypass token for the client's admin, so no writes were accepted.
This is the part that made zero data loss possible. The app was read-only, not "mostly working". A queue worker that accepts a job and then dies mid-write is worse than a queue that is paused. I would rather explain a 38-minute read-only window than reconcile a half-written invoice.
15:20 — The Rollback Ledger
At 15:20 the migration and indexes were done, but the queue worker would not start. The new job class referenced a config key that was missing from the production .env. On a normal deploy I would have caught it in the smoke test at 13:20. On 4G I was running the smoke test at 15:20.
The ledger is a plain markdown file in the repo. Every step gets four fields: time, action, hash or output, and reverse. It looks like this:
| Time | Action | Evidence | Reverse |
|---|---|---|---|
| 13:10 | DB snapshot | snap-20261028-1310.sql.gz |
restore snapshot |
| 13:12 | Maintenance on | bypass token rotated | maintenance off |
| 13:14 | Migration started | 2026_10_28_131400_create_gst_returns |
migrate:rollback --step=1 |
| 14:12 | Migration done | 1 table, 2 indexes | rollback step |
| 14:24 | Index build done | SHOW INDEX 11 rows |
drop indexes |
| 15:20 | Queue restart failed | Missing config: gst.return_window |
add key, restart |
| 15:31 | Queue healthy | 3 workers, 0 failed jobs | stop workers |
| 15:44 | Smoke tests pass | 5/5, P95 240 ms | maintenance on |
The missing key took two minutes to add. The queue came up at 15:31. The smoke tests passed at 15:44. Maintenance mode came off at 15:46.
Without the ledger, I would have been reconstructing the order of events from shell history and memory. With it, the client's analyst could see exactly which steps touched data and which did not. That is what turned a stressful incident into a five-minute status update.
16:05 — Fiber Restored
The ISP restored the link at 16:02. The route metric did its job: the default route moved back to eth0 without a manual change. I verified three things before closing the incident:
ip route show defaultshowed the fiber route with metric 100.- The app's health endpoint returned 200 from an external monitor.
- The queue depth was falling, not growing.
The final deploy step, a cache warm for the new invoice status, finished at 17:08. From the first frozen SSH to the final warm was three hours and fifty minutes. The client's customer-facing app was read-only for 38 minutes and fully down for zero minutes, because maintenance mode served a static page with a 200 status.
| Metric | During outage | After recovery |
|---|---|---|
| Deploy wall time | 3 h 58 m | 18 m baseline |
| Read-only window | 38 m | 0 m |
| Orders queued | 218 | 0 |
| Rows lost | 0 | 0 |
| P95 API latency on 4G | 2.4 s | 180 ms |
| Failed queue jobs | 3 (retried) | 0 |
The Runbook I Now Keep
flowchart TD
A[Link drops] --> B{Server reachable?}
B -- No --> C[Check status page + second link]
B -- Yes --> D[Switch to 4G first]
C --> D
D --> E[Freeze writes + note time]
E --> F[Open rollback ledger]
F --> G{Deploy safe to continue?}
G -- Yes --> H[Use staged artifact only]
G -- No --> I[Rollback to snapshot]
H --> J[Smoke test 5 checks]
J --> K{Links restored?}
K -- Yes --> L[Move route back to fiber]
K -- No --> M[Stay on 4G, monitor data cap]
L --> N[Close incident + write timeline]
M --> N
The runbook has four rules I did not have before:
- Switch first, diagnose second. The phone is not a last resort; it is the first response.
- Never push a full artifact over 4G. Stage it before the window or upload a diff.
- Freeze writes before you touch schema. A read-only window is a feature, not a failure.
- Write the reverse for every step. If you cannot describe how to undo it, do not run it during an outage.
⚡ Production Toolkit & Commercial Architecture:
Prevent release-day disasters and unbilled downtime with battle-tested checklists: download the SaaS Launch Hardening Sentinel (₹1,499 — complete pre-flight deployment audits & zero-downtime rollbacks) and grab the free AI Project Kickoff Skills Kit (FREE — risk matrix & failover scoping). For client agreements that protect against scope creep, check the India Offer & Pricing Kit (₹699). For high-availability infrastructure deployments, consult Deepak Bagada Systems Advisory.
Production Trade-offs & Failure Modes
4G data caps are real. A two-hour hotspot session used 1.8 GB with SSH, logs, and one upload. A video call would have burned through a daily cap in 40 minutes. Keep the backup link for deploys and incident comms, not for YouTube.
Asymmetric upload changes the plan. Download speed is irrelevant when you need to push an artifact. Measure upload before the incident, not during it. If upload is under 2 Mbps, pre-stage.
Route metrics need a test. A failover config that has never been tested is a hypothesis. I test the metric switch once a quarter with a 30-second ip link set on a staging box. The first test found that the phone's interface name changed after a reboot.
Maintenance mode is not a rollback. It hides the app; it does not undo a migration. Keep the snapshot and the reverse steps separate from the maintenance toggle. I have seen teams lift maintenance mode on a half-migrated schema because the page looked fine.
Frequently Asked Questions
How long did the Junagadh internet outage last?
The fiber cut lasted about four hours, from 13:22 to 16:02. The deploy took three hours and fifty-eight minutes in total, against an 18-minute baseline. The customer-facing app was in read-only mode for 38 minutes and never returned a 5xx to visitors.
Did the deploy lose any data?
No rows were lost. The app was put into read-only mode before the migration ran, so no writes could race the schema change. Three queue jobs failed during the worker restart and were retried successfully from the queue backend.
What is a rollback ledger?
It is a timestamped list of every deploy step with the command, the evidence it produced, and the reverse action. It gives the incident a shared timeline and makes rollback a lookup instead of a memory exercise. I keep it in the repo next to the release notes.
How do you prepare for an internet outage during a deploy?
Test a 4G failover route before you need it, stage the release artifact on the server, freeze writes before schema changes, and keep a written reverse for every step. The backup link is the easy part; the discipline to pause writes is the hard part.
📚 Authoritative Primary Sources & Research Citations
- Linux NetworkManager Routing & Multi-Homing Documentation: Metrics-based gateway switching and asymmetric connection management.
- MySQL 8.0 Reference Manual — Metadata Locking (MDL): Managing concurrent DDL locks during production table alterations.
- SaaS Next Production SRE Runbooks (2026): Junagadh lab operational procedures for resilient Tier-3 infrastructure deployments.
Bottom Line
The outage did not teach me anything new about Laravel. It taught me that a deploy is a sequence of reversible steps, and that the network is one of the steps. I now keep a 4G route with a higher metric, a staged artifact, and a rollback ledger for every release. Protect your releases with the SaaS Launch Hardening Sentinel, scope your risk with the AI Project Kickoff Skills Kit, and read the Octane P95 checklist. For reliable production web deployments, explore my web development service or send me your current deploy sequence.