Documentation sections

Recovery after losing PHP sessions

How a new PHP generation starts after temporary state loss — automatically after a server restart or manually — and how to verify the result without resubmitting old forms.

PHP temporary state may disappear when a VPS, Docker or a PHP container restarts. Micator blocks unsafe execution. After a verified server restart, a new PHP session generation starts automatically; in other cases the owner starts it manually. This procedure does not restore lost session contents.

Warning.

The manual procedure applies to PHP contract format2. Automatic recovery is available starting with release 20261101.1, controller 20261101, and requires updating the installation. An article does not update the server; manual recovery requires the owner to explicitly accept session loss.

What happens when state is lost

The new application checks the PHP generation and service markers before invoking a handler. Lost, damaged or unready state prevents execution and a POST is never retried automatically. PHP pool validation may return 503; an old or invalid protected visit may return 404. The previous contract requires manual recovery. In release 20261101.1, a verified OS restart follows the automatic recovery rules; restarting only a service or retrying an update does not authorize automatic recovery. If PHP state was lost, review it and use manual recovery.

When running legacy pools are first pinned, the service persists an ingress barrier, then changes their runtime restart policy to no without stopping or recreating them, and only then records the PHP preservation state. The barrier survives interruption or an OS restart between these steps: an old application must not receive an empty legacy pool. If the update is refused or interrupted after that record but before the backup starts, the service removes the barrier itself (after an interruption, at its next start) while both pools are fenced and match that record: the previous application keeps serving the same pinned pools. Otherwise the barrier stays and php-status reports lost. If removing the barrier at that start fails (for example, Docker does not answer), the service does not try again: the barrier stays until the update is continued with the command below, and php-status reports requires_compatible_release. Do not restore the old restart policy or remove the barrier manually; investigate the cause and continue the original operation.

This blocks PHP with unknown state. It is not a reason to manually shut down postback intake or ordinary external redirects. During recovery itself, the service may fence and restart application processes to drain requests and verify compatible code; this procedure does not promise uninterrupted traffic for the entire tracker.

While recovery keeps the applications stopped, the tracker pauses traffic the same way as a backup: campaign links, hosted landing pages and offers, and postback and lead intake respond with code 503 and the text Tracker maintenance in progress. Clicks and postbacks that arrive during this time are not saved. Outgoing postbacks are not sent and wait in the queue, and panel sections do not load data. The pause ends by itself once compatible applications are running again; with an old application, once the update to a compatible release is complete. PHP stays closed until the recovery is finished.

Automatic recovery after a server restart

Since release 20261101.1, controller 20261101. The new contract automatically starts a new PHP generation after a verified restart of the server itself. Lost sessions and completed form fields are not restored. An old POST is never replayed; the visitor must reopen the campaign link and a new form. For part of the recovery the applications are stopped: until they run again, traffic gets the 503 maintenance answer described above.

The service waits for the applications and verifies the previous state, current release, PHP pools and configuration. It leaves installation, updates, backup restoration and history transfers to their current operations. Changed files, replaced containers or missing service records require administrator review. Restarting only the update service or an individual PHP container does not authorize a session reset.

If Redis stopped before the processing server during the restart, Redis keeps the record of the earlier process. The next start of the same container removes it once the last response is more than 30 seconds old, so such a record blocks neither automatic nor manual recovery. A record of a recreated container is not removed this way, and a record of another processing server is removed by the next start of that server's own container. While such a record exists, recovery is refused; it expires on its own 24 hours after its last response. If that server has been taken out of service, make sure it is really stopped and receives no traffic.

An interrupted automatic recovery resumes the same generation; restarting the controller does not create another. After successful verification, the service stops repeated Docker checks until the recorded state changes. Completion or a persistent failure appears in the owner's notifications; progress remains in the update service journal.

Check sudo micatorctl php-status: the command prints one JSON line, and success contains "state":"ready" and "ready":true. If automatic recovery did not finish, preserve the logs and perform the checks below. Manual php-recover with its existing five confirmations remains available, including continuation of a pending operation after loss of the local host key. A backup from another server does not authorize an automatic session reset on this installation.

Checks for the owner

The installation owner runs the command as root on a managed client installation server. Before running it:

  1. Check the actual processes, slots, PHP pools, nodes and load balancer. An old process must not bypass the closed PHP boundary.
  2. Check HTTPS, CDN and cache bypass for campaigns, hosted paths and click-through routes; remove stale cached responses.
  3. Confirm the rollback and restore boundary. After recovery, an old application or a state snapshot that removes this barrier must not return.
  4. Check PHP handlers and the external recipient with a test form.
  5. Accept the loss of all current PHP sessions, not only one failed pool. Do not resubmit applications an external recipient has already accepted.

Old sess_* files are not copied between pages or generations. A PHPSESSID cookie cannot restore state or prove file ownership.

Check the state

Before taking action, run on the server:

bash
sudo micatorctl php-status

This queries the local root controller. The command prints one JSON line with the format, state, ready, private_ready, legacy_preserved, legacy_retired, php_epoch, controller_min_version and witness_sha256 fields, plus plan_sha256, start, legacy_until and arm_plan when a plan exists. It reports the current PHP generation and readiness, including lost state or an unfinished operation; it does not create a new epoch or start or resume recovery.

  • "state":"ready" and "ready":true mean PHP is verified; no new recovery is needed.
  • lost means the previous temporary state can no longer be verified: a pool restarted, or a marker or the fence is gone.
  • recovering means readiness is not yet confirmed. It can indicate unfinished recovery or an intermediate publication of the root plan after arm. For an interrupted arm with healthy pools, use a confirmed retry of the same arm, not a session reset.
  • requires_compatible_release means a compatible signed release is needed to continue. An interrupted or refused first update that preserves PHP is shown the same way: the running legacy pools are the pinned ones and no session is lost. A confirmed php-recover in this state does not create a new generation or reset sessions; with --target-version of that release it continues the same update (see below).
  • php_epoch and legacy_retired identify the session generation and whether legacy pools are retired. If arm_plan is present, it is the original pinned plan with its ID and start/legacy_until dates. Compare them without extending or creating new dates.

Repeating php-recover while PHP is healthy does not create another generation or lose healthy sessions. With no unfinished job, an ordinary call or --target-version matching the installed version only returns status. An explicit different target can install a previously accepted, pinned compatible candidate while preserving the current epoch; this narrow case is described below. If a recovery update is already switching or committing, the original job takes priority: repeat its target, as another value cannot replace the pending job. Do not automate this command. ready does not mean the update is complete or traffic is open.

Manual command

For an installation already running a compatible application:

bash
sudo micatorctl php-recover \
  --inventory-confirmed \
  --cache-confirmed \
  --restore-confirmed \
  --php-confirmed \
  --accept-session-loss

All five flags are required. The final flag explicitly accepts session loss. Do not add them to an automatic boot script, cron task or normal update command. Initial recovery after actual state loss creates a durable intent; what to do if the command is interrupted is described below.

If another operation that pauses traffic is running when you start recovery — an update, a backup or restore, or a transfer from Keitaro — php-recover refuses before stopping anything and prints “Local controller refused the operation; the PHP recovery journal is preserved.” Finish that operation, then repeat the same command.

The service verifies the application and signed PHP runtime, fences previous execution, drains handlers and creates new private pools. The new PHP epoch concerns sessions and access, not the version of a page's HTML/PHP files.

If an old application is running

A permitted pre-activation rollback may leave incompatible Go processes. Recovery then stays closed and reports requires_compatible_release. Once php-recover has stopped the old application, it stays stopped until a compatible release is installed: the service does not start it again, and traffic gets the 503 maintenance answer. Use an approved compatible signed release through the existing controller:

The same state and command apply when the first update that preserves PHP was refused or interrupted before the application switch. The command's confirmations are still required, but no session is reset here: the update continues from the previously accepted signed bundle.

bash
sudo micatorctl php-recover \
  --inventory-confirmed \
  --cache-confirmed \
  --restore-confirmed \
  --php-confirmed \
  --accept-session-loss \
  --target-version 0.1.0-beta.20261101.1

The same explicit --target-version call also applies after rollback to an old application when php-status already reports healthy PHP. If the target differs from the installed version, the controller can start only the previously accepted, pinned exact signed bundle. This branch does not start a new recovery, rotate the epoch or reset healthy sessions, and does not fall back to the public latest catalogue. If that retained candidate is unavailable, the command refuses without selecting another version. This is a normal cold update: application traffic may pause during the switch even though PHP sessions are preserved. This is not arbitrary version selection or a way around the rollback floor. compatible_update_job identifies a started or resumed job, not its completion.

After initially accepting the signed bundle, the controller retains and pins its exact bytes: archive, signed catalogue and manifest. Repeating the same --target-version can continue with that previously accepted bundle even if the public catalogue now names another version or the earlier catalogue has expired. Signatures, hashes and compatibility are checked again. This narrow exception covers those same pinned bytes, not any old catalogue, an archive with the same name or another source.

If retained files are missing, changed or do not match the pinned candidate, continuation is blocked. Do not delete the cache, journal or root record, or substitute a version to bypass validation: preserve the state and contact support.

Which forms no longer work

Losing one pool requires a new global PHP epoch. After a new generation is actually created because state was lost, all previous protected visits and forms lose access. Installing only a retained candidate while PHP is healthy does not itself change the epoch or invalidate forms through this mechanism. After recovery from state loss, legacy pools are permanently retired; a raw POST to an old path, including a campaign link or /clk, is not converted to a new POST or given an empty new session.

To start again, open the campaign link with a fresh normal GET. It issues a new limited visit and uses private sessions even if separate page protection activation is still in compat. This is an explicit recovery exception to the previous mode. A direct GET to an old PHP path does not upgrade access automatically.

The original start and legacy_until protection plan dates remain unchanged. Recovery neither creates a new 48-hour window nor extends old forms. The old rollback and in-place restore restriction after a new epoch remains regardless of clock changes.

An interruption during recovery

After investigating the cause, repeat the same confirmed command. Continuation uses the original intent, epoch and nonce; it does not create a new protection deadline. Execution stays closed while the application or PHP is unready. Do not remove operation journals, markers or state files, or recreate containers manually.

A manual php-recover that was interrupted by a controller restart or a server reboot does not continue by itself: repeat the same command. Until then the tracker may keep answering 503 (maintenance), and PHP stays closed. The only exception is an update to a compatible release that has already reached the “Creating backup” («Создание резервной копии») stage: the update service continues it on its own; check its result in update history.

The recovering, lost and requires_compatible_release states do not mean completion. ready confirms the PHP generation, not completion of the recovery update: it may still be switching the application or committing the result. Repeat the same confirmed command to continue the same job and PHP generation. compatible_update_job identifies a started or resumed job; check its final result in update operation history. Contact support if the state is unclear.

Check the result

  1. Wait for the original update job to finish successfully and for the compatible application and private pools to become ready. A compatible_update_job or ready alone does not confirm this.
  2. Run sudo micatorctl php-status again: require "state":"ready" and "ready":true. After recovery from state loss, require "legacy_retired":true and a new php_epoch; installing a retained candidate with healthy PHP must preserve the previous generation and legacy_retired value. An old terminal message is not a fresh check.
  3. Open a test campaign link in a separate browser profile, load its resources and submit a new test form. Check the external recipient.
  4. After a generation change, the old protected visit and raw POST must be denied without invoking the handler again. When updating with healthy PHP, verify that an open test form is preserved without resubmitting a real application.
  5. Compare the original protection plan dates and check open traffic, redirects and event intake. Do not replay real applications for testing.

Recovery after actual state loss resumes work with new sessions. It does not recover old form contents, shopping carts, CSRF state or lost temporary files.

Updated Need help? ↗