Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

[PLUGIN] par2cron for UNRAID

Featured Replies

dwpar2cron.png

par2cron for UNRAID

https://github.com/desertwitch/par2cron

https://github.com/desertwitch/par2cron-unRAID

PAR2 Integrity & Self-Repair Engine

Selective automated protection for directory trees

par2cron is a tool that wraps par2cmdline (a parity-based file recovery tool) to achieve automated periodic integrity creation, verification and repair within any given directory tree. It is designed for use with non-changing WORM-type of files, perfect for adding a degree of protection to media libraries or backups. The driving idea is that you do not need to invest in a filesystem (like ZFS) that protects all your data, at the disadvantage of additional complexities, when you really only care that important subsets of your data remain protected.

The array is scanned for marker files, and a PAR2 set created for every directory containing such a _par2cron file. For verification, the program loads the PAR2 sets and verifies that the data which they are protecting is healthy, otherwise flagging the PAR2 set for repair. Once repair runs, corrupted or missing files are recovered. Many command-line tunables, as well as configuration directives, are offered for more granular adjustment of how to create, when to verify and in what situation to repair.

It being set up, you can simply protect any valuable folder by just placing a _par2cron file in it; the tool will create a PAR2 set and pick it up into the periodic verification and repair cycle - now protected from corruption/bitrot (with e.g. 15% redundancy, consuming less disk space than a full-redundancy solution).

Installable via Community Applications

this seems nice, i do wish there was the ability to recursively go into directories though, trying it out.

  • Author
29 minutes ago, oliver said:

this seems nice, i do wish there was the ability to recursively go into directories though, trying it out.

Recursive creation will be possible within the next 1-2 updates, I'm currently working on this (as well as some other features).

I am not ready to migrate to ZFS and likely never will so the subject is very important to me and I just stumbled upon this plugin. So far I have been using File Integrity plugin, but I am no expert on the mechanics involved.

Genuine question, would you recommend your solution and if so why? Is it faster or more resilient or other?

  • Author
13 hours ago, rug said:

I am not ready to migrate to ZFS and likely never will so the subject is very important to me and I just stumbled upon this plugin. So far I have been using File Integrity plugin, but I am no expert on the mechanics involved.

Genuine question, would you recommend your solution and if so why? Is it faster or more resilient or other?

I'm not an expert on the "File Integrity" plugin, but from what I know it only does checksumming.

This means it calculates checksums for all your data, and you can see if something changed (but not repair).

On the other hand, par2cron doesn't protect all your data, but lets you create protection sets for important data.

Those sets it verifies the integrity, so you can see if something changed, and lets you repair in case of corruption (unless severe).

23 hours ago, Rysz said:

I'm not an expert on the "File Integrity" plugin, but from what I know it only does checksumming.

This means it calculates checksums for all your data, and you can see if something changed (but not repair).

On the other hand, par2cron doesn't protect all your data, but lets you create protection sets for important data.

Those sets it verifies the integrity, so you can see if something changed, and lets you repair in case of corruption (unless severe).

Thanks for the answer! So this means with par2cron we could repair on the server whereas with fileintegrity plugin we would have to restore from backup.

From my point of view your solution would help with files with regular updates and give them special most current protection on the host. The question comes down to how secure you want to have it and how often we believe to expect issues and how much effort restore would be.

Edited by perpetuum.mobile

  • Author
On 2/1/2026 at 11:22 AM, perpetuum.mobile said:

Thanks for the answer! So this means with par2cron we could repair on the server whereas with fileintegrity plugin we would have to restore from backup.

From my point of view your solution would help with files with regular updates and give them special most current protection on the host. The question comes down to how secure you want to have it and how often we believe to expect issues and how much effort restore would be.

The idea is quite the opposite actually, it's for files which do not change often and just sit there.

Think your family photos, recordings of your super rare favourite TV series, anything irreplaceable.

These are the files where you won't notice corruption until you open them again in one or two years.

The program checks these files continuously so you can rest at ease that they are still in tip top shape.

Files which you regularly update or interact with, you'll likely notice problems, and for those ZFS is better.

On 1/30/2026 at 9:39 PM, oliver said:

this seems nice, i do wish there was the ability to recursively go into directories though, trying it out.

Recursive mode is added in the latest update, the quick guide and documentation have been updated with how to use it.

  • 2 months later...

This is bloody brilliant! I was considering switching to ZFS, but with this there's really no need.

I'm still testing, but it looks very promising. Could arguably be one of the best plugins for UnRAID ever in my mind ;)

Just out of interest though, why is the default redundancy set to 15%? If this is primarily to address bitrot, the par2 default of 5% is more than adequate, isn't it?

  • Author
4 hours ago, Karools said:

This is bloody brilliant! I was considering switching to ZFS, but with this there's really no need.

I'm still testing, but it looks very promising. Could arguably be one of the best plugins for UnRAID ever in my mind ;)

Just out of interest though, why is the default redundancy set to 15%? If this is primarily to address bitrot, the par2 default of 5% is more than adequate, isn't it?

No special reason, it's just what I use personally and I wanted to show how to set something different than the default. 😉

But you're right, of course, 5% should be just fine for the random bit flip that people will want to guard against ("bit rot")...

The only issue I have so far is that the glob seems rather limited. It only allows for a single extension. There's no brace expansion support, unless I'm doing it wrong.

I'm using this in a media library setting, I'm using a recursive marker per movie/season - that's the idea for now anyway. The problem is that these folders tend to contain lots of images and nfo files that can change quite frequently.

Ideally I would like to add a recursive marker with something like the following to the root of each movie - glob: "**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa}

  • Author
16 minutes ago, Karools said:

The only issue I have so far is that the glob seems rather limited. It only allows for a single extension. There's no brace expansion support, unless I'm doing it wrong.

I'm using this in a media library setting, I'm using a recursive marker per movie/season - that's the idea for now anyway. The problem is that these folders tend to contain lots of images and nfo files that can change quite frequently.

Ideally I would like to add a recursive marker with something like the following to the root of each movie - glob: "**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa}


Thanks for the valid feedback, glob support is indeed relatively (too) limited.

I have good experiences with the doublestar library, would these patterns help you achieve what you want?
https://github.com/bmatcuk/doublestar#patterns

If so, I can probably relatively seamlessly switch the current glob implementation to this more featureful one.

That should work a treat yes, it looks exactly like what we're after.

I'd very much appreciate if you could look into this when you have a moment, I'm fairly certain other users would also find it very useful.

  • Author
7 hours ago, Karools said:

That should work a treat yes, it looks exactly like what we're after.

I'd very much appreciate if you could look into this when you have a moment, I'm fairly certain other users would also find it very useful.

The latest update now contains the changed implementation.

You will need to use either folder (one PAR2 set, see below) or file (one PAR2 set per file) creation modes for any glob possibly spanning multiple folders.

The reason is that recursive mode triggers par2cmdline's "dumb" recursion (which just includes everything in subfolders), and we don't want double-recursion.

See more about this in the documentation, but the program will error anyway if you made a mistake: https://github.com/desertwitch/par2cron#creation-glob-patterns

Glob pattern **/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} (in folder creation mode) will now create for this example folder:

Run All Day (720p)
├── Run All Day.mkv
├── Run All Day.orig.nfo
├── _par2cron
└── sub
    ├── Run All Day.en.sub
    └── Run All Day.idx

One PAR2 set called Run All Day (720p).par2 (in the marker-containing directory) protecting:

      {
        "name": "Run All Day.mkv",
        "size": 5863690768,
        "mode": 511,
        "is_dir": false,
        "mod_time": "2015-06-02T15:13:02.734571313+02:00"
      },
      {
        "name": "sub/Run All Day.en.sub",
        "size": 9330688,
        "mode": 511,
        "is_dir": false,
        "mod_time": "2015-06-02T13:57:01.121948951+02:00"
      },
      {
        "name": "sub/Run All Day.idx",
        "size": 59657,
        "mode": 511,
        "is_dir": false,
        "mod_time": "2015-06-02T13:57:01.503980322+02:00"
      }

I believe this is what you want to achieve with your pattern, but let me know. 😉

Oh my goodness you're quick 😶

The recursion thing makes sense, recursion is basically assumed based on the glob.

Right, bear in mind it's late evening and I've already got a few glasses of wine in me, but I'm fairly certain I haven't made any mistakes.

I have one new issue now that we didn't have before.

With Plex you can give media an anchor or a tag to help prevent things from being mismatched; or a tag to distinguish between editions, like Director's Cut, Extended etc., in the form of curly brackets - https://support.plex.tv/articles/naming-and-organizing-your-movie-media-files/

It seems happy to process a filename with curly braces, even subfolders, but not when it's the folder containing the _par2cron marker.

└── Movies
    └── Big Buck Bunny (2008) {imdb-tt1254207}
        ├── Big Buck Bunny (2008) {imdb-tt1254207} - 1080p 30fps normal.mp4
        ├── Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.idx
        ├── Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.mp4
        ├── Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.srt
        ├── Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.sub
        ├── Featurettes
        │   ├── big_buck_bunny_720p_surround.avi
        │   ├── sub_folder
        │   │   └── BigBuckBunny_320x180.mp4
        │   └── sub_folder with braces {hello}
        │       └── big_buck_bunny_720p_surround.avi
        ├── landscape.jpg
        ├── logo.png
        ├── _par2cron
        └── poster.jpg

That results in an error where it claims it can't find anything:

par2cron create -c ~/par2cron.yaml .
22:24:03 INF Scanning filesystem for jobs... op=create path=/home/acme/test/media
22:24:03 INF Starting to process 1 jobs... op=create path=/home/acme/test/media maxDuration=0s
22:24:03 INF Job started op=create job="/home/acme/test/media/Movies/Big Buck Bunny (2008) {imdb-tt1254207}/_par2cron" job_position=1/1 args=[] glob=**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} mode=folder hidden=false verify=false
22:24:03 ERR No files to protect in folder (will check again next run) op=create path="/home/acme/test/media/Movies/Big Buck Bunny (2008) {imdb-tt1254207}" job="/home/acme/test/media/Movies/Big Buck Bunny (2008) {imdb-tt1254207}/_par2cron" job_position=1/1 args=[] glob=**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} mode=folder hidden=false verify=false
22:24:03 ERR Job failure (will retry next run) op=create job="/home/acme/test/media/Movies/Big Buck Bunny (2008) {imdb-tt1254207}/_par2cron" job_position=1/1 args=[] glob=**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} mode=folder hidden=false verify=false error="failed to find to-protect elements: no files to protect"
22:24:03 ERR Operation completed with errors (1/1 jobs processed) op=create error="partial failure: failed to find to-protect elements: no files to protect" successCount=0 skipCount=0 errorCount=1 processedCount=1 selectedCount=1
Error: create: partial failure: failed to find to-protect elements: no files to protect

But if you remove the braces from the main folder, it happy does its thing, including subfolders with curly braces:

par2cron create -c ~/par2cron.yaml .
22:24:21 INF Scanning filesystem for jobs... op=create path=/home/acme/test/media
22:24:21 INF Starting to process 1 jobs... op=create path=/home/acme/test/media maxDuration=0s
22:24:21 INF Job started op=create job="/home/acme/test/media/Movies/Big Buck Bunny (2008)/_par2cron" job_position=1/1 args=[] glob=**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} mode=folder hidden=false verify=false
Block size: 821036
Source file count: 8
Source block count: 2000
Recovery block count: 100
Recovery file count: 7

Opening: Big Buck Bunny (2008) {imdb-tt1254207} - 1080p 30fps normal.mp4
Opening: Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.idx
Opening: Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.mp4
Opening: Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.srt
Opening: Big Buck Bunny (2008) {imdb-tt1254207} - 2160p 30fps normal.sub
Opening: Featurettes/big_buck_bunny_720p_surround.avi
Opening: Featurettes/sub_folder with braces {hello}/big_buck_bunny_720p_surround.avi
Opening: Featurettes/sub_folder/BigBuckBunny_320x180.mp4
Computing Reed Solomon matrix.
Constructing: done.
Wrote 82103600 bytes to disk
Writing recovery packets
Writing verification packets
Done
22:25:11 INF Succeeded to create PAR2 op=create path="/home/acme/test/media/Movies/Big Buck Bunny (2008)/Big Buck Bunny (2008).par2" job="/home/acme/test/media/Movies/Big Buck Bunny (2008)/_par2cron" job_position=1/1 args=[] glob=**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} mode=folder hidden=false verify=false
22:25:11 INF Job completed with success op=create job="/home/acme/test/media/Movies/Big Buck Bunny (2008)/_par2cron" job_position=1/1 args=[] glob=**/*.{mkv,avi,mp4,idx,sub,srt,ass,ssa} mode=folder hidden=false verify=false
22:25:11 INF Operation completed (1/1 jobs processed) op=create successCount=1 skipCount=0 errorCount=0 processedCount=1 selectedCount=1

Apart from that, it's nearly perfect 🙃

  • Author

Thanks for the quick response and detailed report.

The problem should be fixed with the most recent update of the plugin (2026.04.07d/v0.2.2).

I've got a few more improvements planned, namely I want to add another creation mode "nested".

In nested mode a self-contained PAR2 set shall be created for any matched (sub-)folder's contents.

Placing one marker in a top-level "Movies" would create "Movies/A/A.par2", "Movies/B/B.par2", ... sets.

Marker files could be set to persistent, specifically for "nested", with PAR2 sets for new folders created from one top-level marker file.

The scheduled creation would just pick up that marker file repeatedly, skip folders with existing PAR2 sets, and create sets for those without any.

I figured this could be helpful for growing media libraries with a continuous intake of new content, without requiring marker file recreation.

Updating a PAR2 set could then be as easy as just deleting the old PAR2 set, without ever needing to recreate a marker file... that's how I pictured it.

Anyway this will most likely be part of a more extensive effort, especially on the documentation side, so this'll probably take me a longer while. 😉

any way to limit which shares it searches for _par2cron? takes way too long to search through 15 drives that it shouldnt be searching.

Firstly, the curly bracket issue is indeed fixed now with v0.2.2 - thank you very much. It's working perfectly.

And the nested idea sounds great, it would certainly be of use to people who use *arrs for automation, at least for Movies. For current/ongoing Series not as much, for obvious reasons.. but there are ways to work around that 🤔

By the way, what @paulio23 mentioned is a bit of a problem for me too. I added markers to 4 movies for testing. After manually kicking off the Create task, it took over an hour for it to get going.

Screenshot 2026-04-08 083103.png

  • Author

Thanks for the responses, that definitely feels like it's too long. I'll look into it, how many disks and TBs do you have? Ideally if you know/could check how many files your /mnt/user contains, I could tell if it's a bug or really that slow because of the amount of files. Also, was the indexing always this slow or it got massively worse with this week's update?

I suspect it's the sheer number of small files and folders, but they all sit outside of the of folder I'm actually interested in running this on. If there is a way of limiting the plugin to specific paths, that would be great.

This setup consists of 24 x 12TB SAS drives + 6 SSDs for cache. Overall speed is quite good.

/mnt/user:

time find /mnt/user -type f | wc -l
6570518

real    6m50.679s
user    0m13.044s
sys     0m36.744s



time du -hs /mnt/user
142T    /mnt/user

real    17m1.591s
user    0m17.075s
sys     1m53.070s

/mnt/user/media:

time find /mnt/user/media -type f | wc -l
725466

real    0m22.400s
user    0m0.862s
sys     0m1.901s



time du -hs /mnt/user/media
132T    /mnt/user/media

real    1m42.175s
user    0m1.278s
sys     0m9.025s

As you can see the difference in duration there is rather significant.

I honestly don't know how slow it was prior to the glob updates. I don't recall it being particularly slow or snappy. Most of my tests were done on a different machine.

Edited by Karools

  • Author

Was this during a parity check or something that could impact I/O?

Because I cannot replicate this, a similar setup with 4 million files completes in less than 10 minutes here.

I'm optimising hot paths some more, but I cannot figure out what would cause a one hour filesystem scan.

Unless it's something in your environment that's severely bottle-necking I/O, but your find is much faster...

Anything unusual come to mind about your environment or setup as a whole?

I'll look into allowing choice of path or shares... but still the one hour seems way out of proportion here.

Nope, no parity check or anything too IO intensive.

I had to replace a drive over the weekend, which is actually what prompted me to go looking into the integrity plugin - and ultimately lead me to your fine par2 implementation.

I should have tried this earlier..

Running par2cron against the media share, it completes the scan in 2m24s:

par2cron create -c /boot/config/plugins/dwpar2cron/config.yaml /mnt/user/media
19:04:42 INF Scanning filesystem for jobs... op=create path=/mnt/user/media
19:07:06 INF Starting to process 28 jobs... op=create path=/mnt/user/media maxDuration=0s

Against the whole array, we jump up to 1h7m51s (very similar to yesterday in fact):

par2cron create -c /boot/config/plugins/dwpar2cron/config.yaml /mnt/user
19:07:36 INF Scanning filesystem for jobs... op=create path=/mnt/user
20:15:27 INF Starting to process 28 jobs... op=create path=/mnt/user maxDuration=0s

I don't know, but there's definitely something about the other shares that throws the poor thing off. Maybe I'll run par2cron against each of them and try to narrow it down.

If it's not the amount of files, could it be the amount of folders?

During this scan the drives were practically idle btw. There's one guy streaming something from Plex (another machine) via NFS.

-- Update --

Though not entirely to blame, it looks like the system share is the main culprit. This is the one used for container images and things.

This folder exists only on SSD for me, it's 160GB in total, consisting of 3216305 files and 506124 folders.

And it takes par2cron 34m39s to process:

par2cron create -c /boot/config/plugins/dwpar2cron/config.yaml /mnt/user/system
20:57:56 INF Scanning filesystem for jobs... op=create path=/mnt/user/system
21:32:35 INF Nothing to do (will check again next run) op=create path=/mnt/user/system

Edited by Karools

  • Author

Many thanks for bearing with me here, I think I've identified the core issue.

The problem was that the ignore-file-checker added two stat syscalls per directory, that can quickly scale up.

When initially designing this, I didn't really tune this path for performance thinking most people will run it at night...

But I've now refactored that and reduced it to the one necessary syscall and mostly zero allocations in the hot path.

I'd have a pre-release version ready for testing with the fixes implemented, reducing scanning time by ~50+%.

I'd be very grateful if you could test if this version significantly reduces the scanning time against /mnt/user.

Just take care that you're executing this version and not the old (installed) one...

If it still feels like it's too long, you can run it with par2cron create /mnt/user --pprof perf.pb.gz

That will create a performance profile (just of what the application is doing internally, no paths or sensible data)

If you'd upload (or DM) that for me I can take a look at what is causing the wait and if it's I/O or a congested application path.

Again many thanks for your feedback, it's much appreciated. 😉

par2cron_0.2.2-SNAPSHOT-d7812bf_linux_amd64.tar.gz

Oh no, not at all. This is a critical part of UnRAID for me now, optimisation is important ;D

It's interesting, doing the media share by iteself now the time has doubled, but doing the whole array, it's been cut in half (more or less):

./par2cron -v
par2cron version 0.2.2-SNAPSHOT-d7812bf

./par2cron create -c /boot/config/plugins/dwpar2cron/config.yaml /mnt/user/media
13:21:18 INF Scanning filesystem for jobs... op=create path=/mnt/user/media walker=os
13:27:23 INF Starting to process 28 jobs... op=create path=/mnt/user/media maxDuration=0s


./par2cron create -c /boot/config/plugins/dwpar2cron/config.yaml /mnt/user
13:28:11 INF Scanning filesystem for jobs... op=create path=/mnt/user walker=os
14:03:25 INF Starting to process 28 jobs... op=create path=/mnt/user maxDuration=0s


./par2cron create -c /boot/config/plugins/dwpar2cron/config.yaml /mnt/user --pprof perf.pb.gz
14:31:58 INF Scanning filesystem for jobs... op=create path=/mnt/user walker=os
15:07:53 INF Starting to process 28 jobs... op=create path=/mnt/user maxDuration=0s

It's certainly more manageable; and I guess under normal circumstances it won't matter if it takes 30 min longer to iterate through the entire array.

But still, is it really necessary to do so.

perf.pb.gz

  • Author

Many thanks for the performance profile, it confirms there's no application level bottlenecks anymore.

I/O wait aside, the program would be done in 1.5 minutes for the whole array, but there's significant waiting on I/O.

Meaning it's the program is spending all that time waiting on the disks to seek and return the requested information.

The media share being slower now is likely due to caching effects, maybe more information was cached at the previous run.

      File: par2cron
Type: cpu
Time: 2026-04-09 04:31:58 CEST
Duration: 2157.09s, Total samples = 98.30s ( 4.56%)
Showing nodes accounting for 98.30s, 100% of 98.30s total
----------------------------------------------------------+-------------
      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 
----------------------------------------------------------+-------------
                                            77.84s   100% |   syscall.RawSyscall6 syscall/syscall_linux.go:65
                                             0.01s 0.013% |   internal/runtime/syscall/linux.EpollWait internal/runtime/syscall/linux/syscall_linux.go:32
    77.85s 79.20% 79.20%     77.85s 79.20%                | internal/runtime/syscall/linux.Syscall6 internal/runtime/syscall/linux/asm_linux_amd64.s:36

This is unfortunately the hardware limitation here, so there's nothing more we can optimise except for limiting the scope itself.

I'll get this new version released today together with changes to allow selecting only a specific share to run par2cron against.

I want to allow selecting multiple shares to run it against, but that's a larger task, so for now it'll only be whole array or one specific share.

Thanks again for all your feedback, without it I wouldn't have been able to make these improvements. 😉

  • Author

@paulio23 @Karools


The new version is now released with the performance fixes and the ability to select one or multiple shares to use par2cron on.

Many thanks for your valuable feedback and testing. 💛

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.