<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr" -->

---
title: All Your GUCs in a Row: restore_command | daily.dev
description: PostgreSQL's restore_command has no way to know where a WAL archive truly ends, so it treats any nonzero exit code from 1 to 125 as 'archive exhausted,' even...
canonical: https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: All Your GUCs in a Row: restore_command | daily.dev
og:description: PostgreSQL's restore_command has no way to know where a WAL archive truly ends, so it treats any nonzero exit code from 1 to 125 as 'archive exhausted,' even...
og:url: https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr
og:image: https://api.daily.dev/og/posts/FfPhC4Exr.png
og:image:alt: All Your GUCs in a Row: restore_command
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# All Your GUCs in a Row: restore_command

**[Planet PostgreSQL](https://daily.dev/sources/planet-postgresql)** · 9 min read · 0 upvotes · 0 comments

## Summary

PostgreSQL's restore_command has no way to know where a WAL archive truly ends, so it treats any nonzero exit code from 1 to 125 as 'archive exhausted,' even when that failure actually means a transient network error. A test showed a restore_command that returned exit code 1 once, on the 21st of 43 segments, silently stopped recovery there, leaving 22 segments never requested and no error in the log. The fix is to make the command distinguish 'not found' (exit 1) from 'could not reach archive' (exit 255 or similar), as WAL-G's wal-fetch does with exit 74 for missing segments. The command also runs serially: a 43-segment restore took 3.1 seconds with plain cp, but 13.7 seconds with a 250ms delay per call, since restore_command never runs two fetches concurrently. Tools like pgBackRest and WAL-G avoid this by prefetching segments ahead of time.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://postgr.es/p/9Ae>

## Questions this post answers

### What exit code should a PostgreSQL restore_command return when a WAL segment is missing versus when the archive is unreachable?

Exit status 1 through 125 tells PostgreSQL 'not in the archive,' which ends recovery during a point-in-time restore. Because network failures and missing files often share exit code 1 (as with cp and aws s3 cp), a well-written restore command should reserve exit 1 strictly for 'file does not exist' and use something above 127, such as 255, for connection or transfer failures so PostgreSQL treats them as fatal errors instead of silently ending recovery.

_Anyone scripting WAL restore logic can save working examples and gotchas like this on daily.dev._

### Why did my PostgreSQL point-in-time recovery stop early without logging any error?

PostgreSQL stops archive recovery the moment restore_command returns an exit code between 1 and 125, treating that as proof the archive has no more segments, even if the real cause was a transient failure. In one test, a restore_command that returned exit code 1 once, on segment 21 of 43, caused PostgreSQL to never request the remaining 22 segments, and nothing in the log indicated an error occurred.

_Debugging a silent WAL recovery gap is easier when you've bookmarked the exit-code behavior behind it on daily.dev._

### Why is my PostgreSQL WAL restore so slow even though I have fast storage?

restore_command runs serially and synchronously, calling system() and waiting for each fetch to finish before requesting the next segment, so per-call overhead multiplies by segment count rather than overlapping. A 250ms delay per call turned a 3.1 second, 43-segment restore into 13.7 seconds, with the startup process spending most samples waiting on the 'RestoreCommand' wait event rather than replaying WAL; tools like pgBackRest and WAL-G fix this by prefetching segments ahead of time.

_Teams tuning recovery speed can track prefetching techniques like these on daily.dev before the next incident._

## Similar posts on daily.dev

- [All Your GUCs in a Row: recovery\_end\_command](https://daily.dev/posts/all-your-gucs-in-a-row-recovery-end-command-o6sq0mrzj) · Planet PostgreSQL · 0 upvotes · 0 comments
- [Your backups are green. That does not mean you can restore.](https://daily.dev/posts/your-backups-are-green-that-does-not-mean-you-can-restore--f9dweijcx) · Planet PostgreSQL · 0 upvotes · 0 comments
- [PostgreSQL Recovery Internals](https://daily.dev/posts/postgresql-recovery-internals-fyfrz2lny) · CYBERTEC PostgreSQL · 3 upvotes · 0 comments
- [Your DBA’s Scariest Command Now Runs Itself](https://daily.dev/posts/your-dba-s-scariest-command-now-runs-itself-ifephwbot) · ITNEXT · 1 upvotes · 0 comments
- [Inside branch-based restores in Lakebase Postgres](https://daily.dev/posts/inside-branch-based-restores-in-lakebase-postgres-udhwx0m8z) · Neon · 1 upvotes · 1 comments

---

Tags: [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"All Your GUCs in a Row: restore_command","url":"https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr"},"datePublished":"2026-10-28T04:32:56.782Z","dateModified":"2026-10-28T05:46:38.875Z","description":"PostgreSQL's restore_command has no way to know where a WAL archive truly ends, so it treats any nonzero exit code from 1 to 125 as 'archive exhausted,' even...","image":"https://media.daily.dev/image/upload/s--qPvKM23u--/f_auto/v1722860399/public/Placeholder%2009","thumbnailUrl":"https://media.daily.dev/image/upload/s--qPvKM23u--/f_auto/v1722860399/public/Placeholder%2009","isAccessibleForFree":true,"articleSection":"Planet PostgreSQL","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Planet PostgreSQL","logo":"https://media.daily.dev/image/upload/logos/placeholder.jpg","url":"https://daily.dev/sources/planet-postgresql"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"postgresql","timeRequired":"PT9M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Planet PostgreSQL","item":"https://daily.dev/sources/planet-postgresql"},{"@type":"ListItem","position":3,"name":"All Your GUCs in a Row: restore_command"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/all-your-gucs-in-a-row-restore-command-ffphc4exr#faq","mainEntity":[{"@type":"Question","name":"What exit code should a PostgreSQL restore_command return when a WAL segment is missing versus when the archive is unreachable?","acceptedAnswer":{"@type":"Answer","text":"Exit status 1 through 125 tells PostgreSQL 'not in the archive,' which ends recovery during a point-in-time restore. Because network failures and missing files often share exit code 1 (as with cp and aws s3 cp), a well-written restore command should reserve exit 1 strictly for 'file does not exist' and use something above 127, such as 255, for connection or transfer failures so PostgreSQL treats them as fatal errors instead of silently ending recovery. Anyone scripting WAL restore logic can save working examples and gotchas like this on daily.dev."}},{"@type":"Question","name":"Why did my PostgreSQL point-in-time recovery stop early without logging any error?","acceptedAnswer":{"@type":"Answer","text":"PostgreSQL stops archive recovery the moment restore_command returns an exit code between 1 and 125, treating that as proof the archive has no more segments, even if the real cause was a transient failure. In one test, a restore_command that returned exit code 1 once, on segment 21 of 43, caused PostgreSQL to never request the remaining 22 segments, and nothing in the log indicated an error occurred. Debugging a silent WAL recovery gap is easier when you've bookmarked the exit-code behavior behind it on daily.dev."}},{"@type":"Question","name":"Why is my PostgreSQL WAL restore so slow even though I have fast storage?","acceptedAnswer":{"@type":"Answer","text":"restore_command runs serially and synchronously, calling system() and waiting for each fetch to finish before requesting the next segment, so per-call overhead multiplies by segment count rather than overlapping. A 250ms delay per call turned a 3.1 second, 43-segment restore into 13.7 seconds, with the startup process spending most samples waiting on the 'RestoreCommand' wait event rather than replaying WAL; tools like pgBackRest and WAL-G fix this by prefetching segments ahead of time. Teams tuning recovery speed can track prefetching techniques like these on daily.dev before the next incident."}}]}
```

