Lidarr v1.1.2.2890 or newer required.
Lidarr doesn't back up Postgres databases. Set up and maintain your own backups.
Lidarr uses Npgsql 9.x, which targets all currently supported PostgreSQL versions (PostgreSQL 14 through 18). PostgreSQL 13 reached end-of-life in November 2025 and isn't recommended.
First, you need a Postgres instance. This guide covers the postgres:17 Docker image.
Don't use the
latesttag. It will upgrade your database engine on container recreate and can break compatibility.
docker create --name=postgres17 \
-e POSTGRES_PASSWORD=qstick \
-e POSTGRES_USER=qstick \
-e POSTGRES_DB=lidarr-main \
-p 5432:5432/tcp \
-v /path/to/appdata/postgres17:/var/lib/postgresql/data \
postgres:17
Lidarr needs two databases. The default names are:
lidarr-main: stores all configuration and historylidarr-log: stores events that produce a log entryLidarr won't create the databases for you. Create them before starting Lidarr.
Create the databases using your preferred tool, for example pgAdmin or Adminer.
You can give the databases any name you want, but make sure config.xml has the correct names. See Schema creation below.
Tell Lidarr to use Postgres. Open config.xml and add or update these entries:
<PostgresUser>qstick</PostgresUser>
<PostgresPassword>qstick</PostgresPassword>
<PostgresPort>5432</PostgresPort>
<PostgresHost>postgres17</PostgresHost>
To use custom database names, also add:
<PostgresMainDb>MainDbName</PostgresMainDb>
<PostgresLogDb>LogDbName</PostgresLogDb>
Only after creating both databases, start Lidarr to initialize the schema.
If you don't want to migrate an existing SQLite database to Postgres, you're done.
Lidarr doesn't officially support SQLite-to-Postgres migration. The community script below works for many users on Postgres 14–17, but isn't guaranteed to work on Postgres 18 or later. For Postgres 18+, start with a fresh database.
To migrate data, use PGLoader. Two gotchas:
--with "quote identifiers" to make them case-sensitive.Don't drop any tables in the Postgres instance.
Before migrating, run Lidarr against the created Postgres databases at least once so the schema exists, then stop Lidarr and follow these steps:
Stop Lidarr.
Open your preferred database management tool and connect to the Postgres instance.
Run the following commands to clear the default seed data:
DELETE FROM "QualityProfiles";
DELETE FROM "QualityDefinitions";
DELETE FROM "DelayProfiles";
DELETE FROM "Metadata";
DELETE FROM "MetadataProfiles";
Start the migration using one of these options:
pgloader --with "quote identifiers" --with "data only" lidarr.db 'postgresql://qstick:qstick@localhost/lidarr-main'
docker run --rm -v /absolute/path/to/lidarr.db:/lidarr.db:ro --network=host ghcr.io/roxedus/pgloader --with "quote identifiers" --with "data only" /lidarr.db "postgresql://qstick:qstick@localhost/lidarr-main"
If pgloader errors on a large database, add
--with "prefetch rows = 100" --with "batch size = 1MB"to the command.
If you see sequence errors after migration, run the following to reset all sequences:
select setval('public."AlbumReleases_Id_seq"', (SELECT MAX("Id")+1 FROM "AlbumReleases"));
select setval('public."Albums_Id_seq"', (SELECT MAX("Id")+1 FROM "Albums"));
select setval('public."ArtistMetadata_Id_seq"', (SELECT MAX("Id")+1 FROM "ArtistMetadata"));
select setval('public."Artists_Id_seq"', (SELECT MAX("Id")+1 FROM "Artists"));
select setval('public."Blacklist_Id_seq"', (SELECT MAX("Id")+1 FROM "Blocklist"));
select setval('public."Commands_Id_seq"', (SELECT MAX("Id")+1 FROM "Commands"));
select setval('public."Config_Id_seq"', (SELECT MAX("Id")+1 FROM "Config"));
select setval('public."CustomFilters_Id_seq"', (SELECT MAX("Id")+1 FROM "CustomFilters"));
select setval('public."CustomFormats_Id_seq"', (SELECT MAX("Id")+1 FROM "CustomFormats"));
select setval('public."DelayProfiles_Id_seq"', (SELECT MAX("Id")+1 FROM "DelayProfiles"));
select setval('public."DownloadClients_Id_seq"', (SELECT MAX("Id")+1 FROM "DownloadClients"));
select setval('public."DownloadClientStatus_Id_seq"', (SELECT MAX("Id")+1 FROM "DownloadClientStatus"));
select setval('public."DownloadHistory_Id_seq"', (SELECT MAX("Id")+1 FROM "DownloadHistory"));
select setval('public."ExtraFiles_Id_seq"', (SELECT MAX("Id")+1 FROM "ExtraFiles"));
select setval('public."History_Id_seq"', (SELECT MAX("Id")+1 FROM "History"));
select setval('public."ImportListExclusions_Id_seq"', (SELECT MAX("Id")+1 FROM "ImportListExclusions"));
select setval('public."ImportLists_Id_seq"', (SELECT MAX("Id")+1 FROM "ImportLists"));
select setval('public."ImportListStatus_Id_seq"', (SELECT MAX("Id")+1 FROM "ImportListStatus"));
select setval('public."Indexers_Id_seq"', (SELECT MAX("Id")+1 FROM "Indexers"));
select setval('public."IndexerStatus_Id_seq"', (SELECT MAX("Id")+1 FROM "IndexerStatus"));
select setval('public."LyricFiles_Id_seq"', (SELECT MAX("Id")+1 FROM "LyricFiles"));
select setval('public."Metadata_Id_seq"', (SELECT MAX("Id")+1 FROM "Metadata"));
select setval('public."MetadataFiles_Id_seq"', (SELECT MAX("Id")+1 FROM "MetadataFiles"));
select setval('public."MetadataProfiles_Id_seq"', (SELECT MAX("Id")+1 FROM "MetadataProfiles"));
select setval('public."NamingConfig_Id_seq"', (SELECT MAX("Id")+1 FROM "NamingConfig"));
select setval('public."Notifications_Id_seq"', (SELECT MAX("Id")+1 FROM "Notifications"));
select setval('public."PendingReleases_Id_seq"', (SELECT MAX("Id")+1 FROM "PendingReleases"));
select setval('public."Profiles_Id_seq"', (SELECT MAX("Id")+1 FROM "QualityProfiles"));
select setval('public."QualityDefinitions_Id_seq"', (SELECT MAX("Id")+1 FROM "QualityDefinitions"));
select setval('public."RemotePathMappings_Id_seq"', (SELECT MAX("Id")+1 FROM "RemotePathMappings"));
select setval('public."Restrictions_Id_seq"', (SELECT MAX("Id")+1 FROM "ReleaseProfiles"));
select setval('public."RootFolders_Id_seq"', (SELECT MAX("Id")+1 FROM "RootFolders"));
select setval('public."ScheduledTasks_Id_seq"', (SELECT MAX("Id")+1 FROM "ScheduledTasks"));
select setval('public."Tags_Id_seq"', (SELECT MAX("Id")+1 FROM "Tags"));
select setval('public."TrackFiles_Id_seq"', (SELECT MAX("Id")+1 FROM "TrackFiles"));
select setval('public."Tracks_Id_seq"', (SELECT MAX("Id")+1 FROM "Tracks"));
select setval('public."Users_Id_seq"', (SELECT MAX("Id")+1 FROM "Users"));
Start Lidarr.
Lidarr's built-in backup covers only the application config (
config.xml) and, when using SQLite, the database files. It does not back up a Postgres database. Restoring a Lidarr backup will not restore your Postgres data.
Use pg_dump to back up each database separately. The examples below use the postgres:17 Docker container from the setup above. Adjust the container name, user, and database names to match your environment.
Native Postgres install:
pg_dump -U qstick -F c -f /path/to/backups/lidarr-main.dump lidarr-main
pg_dump -U qstick -F c -f /path/to/backups/lidarr-log.dump lidarr-log
Docker:
docker exec postgres17 pg_dump -U qstick -F c lidarr-main > /path/to/backups/lidarr-main.dump
docker exec postgres17 pg_dump -U qstick -F c lidarr-log > /path/to/backups/lidarr-log.dump
The -F c flag uses the custom format, which is compressed and supports selective restore with pg_restore. Use -F p for a plain SQL file if you prefer something human-readable.
Stop Lidarr.
Drop and recreate the target databases so you start from a clean state:
DROP DATABASE "lidarr-main";
DROP DATABASE "lidarr-log";
CREATE DATABASE "lidarr-main";
CREATE DATABASE "lidarr-log";
Restore each dump:
Native Postgres install:
pg_restore -U qstick -d lidarr-main /path/to/backups/lidarr-main.dump
pg_restore -U qstick -d lidarr-log /path/to/backups/lidarr-log.dump
Docker:
docker exec -i postgres17 pg_restore -U qstick -d lidarr-main < /path/to/backups/lidarr-main.dump
docker exec -i postgres17 pg_restore -U qstick -d lidarr-log < /path/to/backups/lidarr-log.dump
Start Lidarr.
Keep several days of rolling backups. A single backup file that gets overwritten daily gives you no recovery point if the database was already corrupted before the last dump ran.
Store dumps in a location that is included in your normal system or offsite backup.
Use cron to schedule a daily dump. The example below backs up the main database and removes dumps older than 7 days:
0 2 * * * pg_dump -U qstick -F c lidarr-main > /path/to/backups/lidarr-main-$(date +\%Y\%m\%d).dump && find /path/to/backups -name "lidarr-main-*.dump" -mtime +7 -delete
Add a second entry for the log database if you want to preserve it as well.
The scripts below are provided for convenience and reference only. They are not supported by the Servarr team. Review them before running and adapt them to your environment.
The following PowerShell script creates a scheduled task that backs up both databases daily and removes dumps older than the retention period. Run it once as Administrator. Re-run it with updated parameters to change the schedule or connection details.
Prerequisite: pg_dump must be available on the host. If you installed Postgres natively, the client tools are included. If you are running Postgres via Docker with no native install, install the PostgreSQL client tools separately, or use the Docker pg_dump approach from the Creating a backup section above instead of this script.
# New-LidarrBackupTask.ps1
# Creates a Windows scheduled task that backs up Lidarr's Postgres databases.
#
# Parameters:
# -PgHost Postgres host (default: localhost)
# -PgPort Postgres port (default: 5432)
# -PgUser Postgres username (default: qstick)
# -PgPassword Postgres password (default: qstick)
# -MainDb Main database name (default: lidarr-main)
# -LogDb Log database name (default: lidarr-log)
# -BackupDir Directory for dump files (default: %ProgramData%\Lidarr\Backups\postgres)
# -RetentionDays Days of backups to keep (default: 7)
# -RunAt Daily run time HH:mm (default: 02:00)
# -TaskName Scheduled task name (default: Lidarr Postgres Backup)
# -PgDumpPath Full path to pg_dump.exe (optional) (default: auto-detect)
param(
[string]$PgHost = "localhost",
[int] $PgPort = 5432,
[string]$PgUser = "qstick",
[string]$PgPassword = "qstick",
[string]$MainDb = "lidarr-main",
[string]$LogDb = "lidarr-log",
[string]$BackupDir = "$env:ProgramData\Lidarr\Backups\postgres",
[int] $RetentionDays = 7,
[string]$RunAt = "02:00",
[string]$TaskName = "Lidarr Postgres Backup",
[string]$PgDumpPath = ""
)
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Error "Run this script as Administrator."
exit 1
}
# Resolve pg_dump before writing the backup script so we fail early with a clear message.
if ($PgDumpPath) {
if (-not (Test-Path $PgDumpPath)) {
Write-Error "pg_dump not found at '$PgDumpPath'."
exit 1
}
$resolvedPgDump = $PgDumpPath
} elseif (Get-Command 'pg_dump' -ErrorAction SilentlyContinue) {
$resolvedPgDump = 'pg_dump'
} else {
$found = Get-ChildItem 'C:\Program Files\PostgreSQL' -Filter 'pg_dump.exe' -Recurse -ErrorAction SilentlyContinue |
Sort-Object FullName -Descending |
Select-Object -First 1
if ($found) {
$resolvedPgDump = $found.FullName
} else {
Write-Error "pg_dump not found. Install PostgreSQL client tools, add the bin directory to PATH, or pass -PgDumpPath with the full path to pg_dump.exe."
exit 1
}
}
$installDir = "$env:ProgramData\Lidarr"
$scriptPath = Join-Path $installDir "Invoke-LidarrBackup.ps1"
foreach ($dir in $installDir, $BackupDir) {
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
}
# Write the backup script with connection details embedded.
# To change settings, re-run New-LidarrBackupTask.ps1 rather than editing this file.
@"
`$pgDump = '$resolvedPgDump'
`$PgHost = '$PgHost'
`$PgPort = $PgPort
`$PgUser = '$PgUser'
`$PgPassword = '$PgPassword'
`$MainDb = '$MainDb'
`$LogDb = '$LogDb'
`$BackupDir = '$BackupDir'
`$RetentionDays = $RetentionDays
`$env:PGPASSWORD = `$PgPassword
`$date = Get-Date -Format 'yyyyMMdd-HHmmss'
if (-not (Test-Path `$BackupDir)) { New-Item -ItemType Directory -Path `$BackupDir | Out-Null }
function Invoke-Dump {
param([string]`$Db, [string]`$Label)
`$file = Join-Path `$BackupDir "`$Label-`$date.dump"
& `$pgDump -h `$PgHost -p `$PgPort -U `$PgUser -F c -f `$file `$Db
if (`$LASTEXITCODE -ne 0) {
Write-Error "Backup of `$Db failed (exit code `$LASTEXITCODE)."
exit 1
}
Write-Host "Backed up `$Db to `$file"
}
Invoke-Dump -Db `$MainDb -Label 'lidarr-main'
Invoke-Dump -Db `$LogDb -Label 'lidarr-log'
Get-ChildItem `$BackupDir -Filter '*.dump' |
Where-Object { `$_.LastWriteTime -lt (Get-Date).AddDays(-`$RetentionDays) } |
ForEach-Object {
Remove-Item `$_.FullName
Write-Host "Removed old backup: `$(`$_.Name)"
}
`$env:PGPASSWORD = ''
"@ | Set-Content -Path $scriptPath -Encoding UTF8
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -Daily -At $RunAt
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 1) -StartWhenAvailable
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName $TaskName `
-Action $action -Trigger $trigger -Settings $settings -Principal $principal `
-Force | Out-Null
Write-Host "Task '$TaskName' registered. Backups will run daily at $RunAt."
Write-Host "Backup script: $scriptPath"
Write-Host "Dump directory: $BackupDir"
Write-Host "Using pg_dump: $resolvedPgDump"
Run the script from an elevated PowerShell prompt:
.\New-LidarrBackupTask.ps1 -PgPassword "yourpassword" -RetentionDays 14
If pg_dump is not in PATH and not under C:\Program Files\PostgreSQL, pass the full path explicitly:
.\New-LidarrBackupTask.ps1 -PgPassword "yourpassword" -PgDumpPath "C:\Program Files\PostgreSQL\17\bin\pg_dump.exe"
The script resolves pg_dump at setup time and bakes the path into the generated backup script, so the scheduled task does not depend on PATH being set correctly for the SYSTEM account. It writes Invoke-LidarrBackup.ps1 to %ProgramData%\Lidarr\ with your connection details embedded, then registers a daily scheduled task running as the SYSTEM account. To update the schedule or connection details, re-run New-LidarrBackupTask.ps1 with the new parameters rather than editing the generated script directly.
By default, dumps are written to %ProgramData%\Lidarr\Backups\postgres, alongside Lidarr's own backup files. If you are running multiple Servarr apps against the same Postgres instance, use -BackupDir to specify a shared location instead:
.\New-LidarrBackupTask.ps1 -PgPassword "yourpassword" -BackupDir "D:\backups\servarr\postgres"
The password is stored in plain text inside the generated script. Restrict access to
%ProgramData%\Lidarr\to administrator accounts only.