Linux

Best Photo Scanners for Old Photos: Digitize Prints into Immich

This post contains affiliate links. If you buy through them, we may earn a small commission at no extra cost to you. Learn more.

When someone hands you a shoebox of family prints, the scanner is half the job and the date is the other half. A fresh scan carries today’s timestamp, so Immich files your 1987 wedding photos under the month you scanned them unless you fix that before or after the upload. The best photo scanner for old photos matches the size of the pile and the computer you scan on: for most families, a sheet-fed Epson FastFoto, a cheap flatbed or a phone app.

Original content from computingforgeeks.com - post 177787

This guide compares those three routes across five scanners and two apps, then covers the part most buyer guides skip: getting the scans into a self-hosted Immich server with the right dates, captions and places. Without the scanners on a bench, the picks rest on maker spec sheets, live Amazon listings, PCMag’s tests and the Linux driver lists. Every exiftool and Immich CLI step was run in October 2026 against Immich v3.3.5 on an Ubuntu 26.04 VM, and the output below comes from that box.

Picking the best photo scanner for old photos

Start from how many prints you have and whether you’ll scan from Linux, not from the dpi number on the box. Every pick below scans a 4×6 print at more resolution than the print holds, so the real differences are speed, film support and drivers:

  • Best overall for big piles: Epson FastFoto FF-680W, a 36-photo feeder at about one photo per second (about $530, check the live price).
  • Best for Linux: Canon CanoScan LiDE 400, the only flatbed in this guide that SANE marks Complete (about $90, check the live price).
  • Best budget flatbed: Epson Perfection V39 II, a good photo flatbed with no confirmed Linux driver (seen at $105 to $120, check the live price).
  • Cheapest feeder: Plustek ePhoto Z300, one print at a time, Windows and macOS only (about $220).
  • Best for negatives and slides: Epson Perfection V850 Pro, the film scanner to buy if the box holds strips and slides (roughly $1,700 to $2,000, check the live price).

A phone app costs nothing and is fine for a few dozen prints, but it photographs the print through glare and tilt rather than scanning it. Past a few hundred prints, the FastFoto’s feeder pays for itself in evenings saved, and the date work in the second half of this guide is the same whichever device made the files.

Testing the scanners and the Immich import

We checked the two halves of this guide in different ways, and you should know which is which before you trust a number. The scanner specs come from each maker’s own page, cross-checked against the current Amazon listing and PCMag’s 2026 photo scanner tests, and every Linux claim traces to the SANE supported-devices list or the model files inside Epson’s Linux driver. We didn’t scan prints on these machines, so you won’t find our own speed figures for them.

The Immich half is hands-on. We built test scans that behave the way scanner files do in the wild:

  • a file with no EXIF date at all, like most flatbed output
  • a file stamped with the scan time, the way scanner apps stamp it
  • files dated from their file name, by hand, and in a batch for one roll
  • an XMP sidecar next to an untouched original
  • a stripped file whose only date is its modification time

Each one went through the Immich CLI into a fresh server, and we read back the date Immich chose for it. Those results drive the advice on which fix to use, because Immich treats each case differently.

Comparing FastFoto, flatbed and phone scanning

Only the decision specs are in the table. Prices move weekly on scanners, so treat the last column as a band and check the live listing:

ScannerTypeOptical resolution4×6 print speedFilmLinuxPrice band
Epson FastFoto FF-680WSheet-fed, 36-photo tray600 dpi (1200 dpi output)About 1 s at 300 dpiNoEpson Scan 2 and SANE epsondsAbout $530
Canon CanoScan LiDE 400Flatbed, USB-C4800 dpiAbout 4 s at 300 dpiNoSANE pixma, CompleteAbout $90
Epson Perfection V39 IIFlatbed, USB4800 dpiLetter page in 10 sNoNot listed$105 to $120
Plustek ePhoto Z300Sheet-fed, one at a time300 or 600 dpiAbout 2 sNoNoneAbout $220
Epson Perfection V850 ProFlatbed with film lid6400 dpiNot published35mm, medium format, 4×5SANE epson2, Good$1,700 to $2,000
Phone appCameraDepends on the phoneA few secondsNon/aFree to $19.99 a month

Speed and drivers decide most purchases. Speed only matters past a few hundred prints, and there the FastFoto’s feeder is in a different class from anything with a lid. Linux support splits the field cleanly: the LiDE 400, the FastFoto and the V850 Pro have drivers you can install today, while the V39 II and the Z300 need a Windows or Mac machine to scan from. If your only computer runs Linux, that column decides it before price does.

Scanning shoeboxes fast: Epson FastFoto FF-680W

The FastFoto FF-680W is the scanner to buy when the pile is measured in shoeboxes. It’s a one-pass duplex sheet feeder with a 36-photo tray, and Epson rates it at 80 photos a minute for 4×6 prints at 300 dpi, which is where the one-photo-a-second claim comes from. It takes wallet prints up to 8×10, Polaroids, and panoramas up to 8.5 by 36 inches, over USB 3.0 or Wi-Fi. PCMag named it the best high-speed snapshot scanner in its 2026 photo scanner roundup.

Epson FastFoto FF-680W sheet-fed photo scanner for digitizing old photos
Epson FastFoto FF-680W: 36-photo feeder, about one 4×6 print per second at 300 dpi. Image: Epson.

Most of its value sits in Epson’s FastFoto app. The app can scan the back of a print only when it detects handwriting there, and it can restore faded colours and save an enhanced copy next to the untouched one, per Epson’s settings reference. It can also put a subject and date on a batch, but that text goes into the file names, and the app’s own date editor is Windows only. FastFoto uses its own naming format for that text, so look at the names it actually produces before you reuse the exiftool file name step later in this guide; rename the files to a YYYY-MM-DD_ prefix or adapt the pattern first.

On Linux the FastFoto app is not an option, since it runs on Windows and macOS only. The scanner itself works through Epson Scan 2 and the SANE epsonds backend, which lists the FF-680W as Complete (SANE marks that backend unmaintained), but you lose the back-of-photo detection and the colour restore. Plenty of people scan from a Mac or Windows laptop and copy the folder to the server, and that’s the setup we’d keep.

Pass on it with fewer than a few hundred prints, or when the prints are curled, torn or stuck to album pages. Fragile prints should not go through a feeder at all, and PCMag found it slower at photos than the model it replaced. It sells for about $530 on Amazon at the time of writing; check the live price.

Scanning on Linux: Canon CanoScan LiDE 400

For a Linux desk, the LiDE 400 is the flatbed we would buy. SANE’s supported-devices list marks it Complete on the pixma backend with every resolution up to 4800 dpi supported, so Simple Scan or any other SANE front end sees it without a vendor driver. Canon ships Windows and macOS software only, and you will not miss it on Linux.

Canon CanoScan LiDE 400 flatbed scanner that works on Linux through SANE
Canon CanoScan LiDE 400: 4800 dpi, USB-C powered, SANE pixma support. Image: Canon.

Canon rates it at 4800 by 4800 dpi with a 48-bit colour input, and a 4×6 print at 300 dpi takes about 4 seconds. It runs off one USB-C cable, and the lid lifts to take an album up to 21 mm thick. PCMag picked it as the flatbed for most people, praising its photo colour, and the trade is the one every flatbed makes: you lift the lid for every print or small group of prints.

It has no film holder and no feeder, so look elsewhere if you need either. It’s about $90 on Amazon right now; check the live price.

Spending less on a flatbed: Epson Perfection V39 II

Linux users should know one thing first about the V39 II: we could not confirm a Linux driver for it, even though it is a good photo flatbed. The Epson Scan 2 release we could inspect lists the original V39 and the V19, not the II models, and SANE has no row for it either. Do not buy it for a Linux-only setup on the hope that the old V39 driver will cover it.

Epson Perfection V39 II budget flatbed scanner for old photos
Epson Perfection V39 II: 4800 dpi, USB-powered, no film support. Image: Epson.

On Windows or a Mac it’s an easy recommendation. It scans at 4800 dpi, does a letter page in about 10 seconds, draws power from USB and weighs 3.1 lb. PCMag rated it the best budget flatbed for photos and liked how it splits several prints on the glass into separate files. It cannot scan film.

On Linux, buy the LiDE 400 instead, which costs less and works. We saw it at $105 and $118 from third-party sellers on the same day; check the live price.

Feeding prints one at a time: Plustek ePhoto Z300

Plustek’s Z300 is the cheapest way to get a feeder, and it shows. You feed each print by hand, Plustek rates it at about 2 seconds per 4×6 print, it scans at 300 or 600 dpi, and Plustek ships its software for Windows and macOS only, with no SANE support. PCMag liked the price and the speed against flatbeds, and called its photo quality mediocre.

Plustek ePhoto Z300 compact sheet-fed photo scanner
Plustek ePhoto Z300: hand-fed sheet scanner, 300 or 600 dpi. Image: Plustek.

It fits a middle case: a few hundred prints, a Windows or Mac laptop, and a budget that won’t stretch to the FastFoto. It costs less than half as much, but it gives up the 36-photo tray, the back-of-print scan and some quality. Avoid it on Linux, or when colour accuracy matters. It’s about $220 from Plustek’s Amazon store; check the live price.

Scanning negatives and slides: Epson Perfection V850 Pro

If the shoebox holds negatives, scan those instead of the prints. A negative carries more detail than the drugstore print made from it. None of the cheaper picks scan film. The V850 Pro does, and it’s still on sale from Epson: a dual-lens flatbed rated at 6400 dpi with a 4.0 Dmax, holders for 35mm strips and slides, medium format and 4×5 sheets, and Digital ICE dust removal for film and prints.

Epson Perfection V850 Pro flatbed scanner for negatives, slides and prints
Epson Perfection V850 Pro: 6400 dpi, 4.0 Dmax, 35mm to 4×5 film holders. Image: Epson.

Linux is covered here too: SANE rates it Good on the epson2 backend, and Epson Scan 2 lists it. The long-time cheaper option, the Perfection V600, is now discontinued on Epson’s site, and its only Amazon listing is a single third-party unit at about $1,300, so we left it out.

With only prints in the box, a $90 flatbed scans them just as usefully. Expect around $1,700 from a third-party Amazon seller and about $2,000 at Epson, and at that price check that the seller is an authorised Epson reseller so the warranty holds; check the live price.

Using a phone app instead of a scanner

A phone app makes sense for a few dozen prints, a visit to a relative who won’t let the album leave the house, or a test run before you buy hardware. It takes a photograph of the print, not a scan, so glare, tilt and uneven light are your problem to manage. The two worth knowing about:

  • Google PhotoScan: free on Android and iPhone, with a glare removal step that Google says you can turn off to scan faster, but its Play Store listing hasn’t been updated since July 2023.
  • Photomyne: lets you tag dates and places and attach the back of a print, with ads on the free tier and paid plans at $19.99 a month or $59.99 a year.

We did not test which date either app writes into its files, and neither vendor documents it. Treat app output like any scan: run the exiftool check in the next sections before the upload, because a capture-time stamp will land every print on the day you took the photo.

Choosing dpi, file format and the scanner type

Most of the mistakes in a scanning project are made in the first hour, and they’re expensive to redo once the prints are back in the loft. These are the settings and checks to decide before the first batch:

  • 300 dpi for 4×6 prints you’ll only view on screens, 600 dpi for prints you might crop or reprint; Epson’s own guide gives the same split
  • a glass flatbed for curled, torn or glued prints, never a feeder
  • the Linux driver checked against SANE’s list by USB ID before you buy
  • one folder per event or roll, so the Immich CLI can turn folders into albums
  • a date in every file name you can date, such as 1992-12-25_christmas.jpg

Of all those, the file name habit matters most. A print with no date on it lands in the wrong year no matter which scanner made it, and a dated name is the one piece of information you can turn into metadata in bulk later. When you only know the year, pick a convention such as 1 July and stick to it, so those prints group together in the timeline.

Checking what date the scanner wrote

Before fixing anything, look at what the scan actually carries. Install exiftool, plus jq for the Immich API checks later, from the Ubuntu or Debian repositories:

sudo apt install -y libimage-exiftool-perl jq

Then print every date tag in the file, with the group it came from:

exiftool -time:all -a -G1 scan-0002.jpg

This test file was stamped with the scan time, the way scanner apps stamp their output:

[System]        File Modification Date/Time     : 2026:10:26 18:30:02+00:00
[System]        File Access Date/Time           : 2026:10:26 18:30:02+00:00
[System]        File Inode Change Date/Time     : 2026:10:26 18:30:02+00:00
[ExifIFD]       Date/Time Original              : 2026:10:26 18:30:01

Apart from the rarer sub-second tags, Immich reads the [ExifIFD] Date/Time Original line before anything else, and here it says October 2026 for a print from decades earlier. When that line is missing, Immich falls back on the earliest of the file’s creation and modification times, which for a fresh scan is also today. Either way the print lands on the scan date, so every file needs one of the fixes below.

Fixing scan dates with exiftool before upload

Writing the date into the file before the upload is the fix we’d use for anything you can date in bulk, because the date then travels with the file into any photo app, not only Immich. Each command below edits the file in place with -overwrite_original, so run it on a copy of the scans folder until you trust it.

Taking the date from the file name

The obvious command for a file named 1992-12-25_christmas.jpg fails, because the name holds a date but no time:

exiftool "-DateTimeOriginal<Filename" -overwrite_original 1992-12-25_christmas.jpg

exiftool refuses it and changes nothing.

Warning: Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z]) in ExifIFD:DateTimeOriginal (PrintConvInv) - 1992-12-25_christmas.jpg
    0 image files updated
    1 image files unchanged

Adding -d '%Y-%m-%d' doesn’t help either; it fails with Warning: No writable tags set. What worked was cutting the name at the first underscore and appending a noon time:

exiftool '-DateTimeOriginal<${Filename;s/_.*//} 12:00:00' -overwrite_original 1992-12-25_christmas.jpg

The re-check now shows the print’s real date.

[System]        File Modification Date/Time     : 2026:10:26 18:30:03+00:00
[System]        File Access Date/Time           : 2026:10:26 18:30:03+00:00
[System]        File Inode Change Date/Time     : 2026:10:26 18:30:03+00:00
[ExifIFD]       Date/Time Original              : 1992:12:25 12:00:00

Noon is deliberate. A midnight time sits one timezone shift away from the previous day, and a print rarely has a known hour anyway.

Some scanner and phone files also carry a CreateDate with an offset tag, and Immich ranks the sub-second create date that exiftool builds from them above DateTimeOriginal. For those files, write every date tag at once with -AllDates. This file started with a scan-time CreateDate and Offset Time Digitized:

exiftool '-AllDates<${Filename;s/_.*//} 12:00:00' -overwrite_original 1975-03-08_party.jpg

All three standard date tags now hold the print’s date, and the composite create date follows them:

[System]        File Modification Date/Time     : 2026:10:26 18:59:54+00:00
[System]        File Access Date/Time           : 2026:10:26 18:59:54+00:00
[System]        File Inode Change Date/Time     : 2026:10:26 18:59:54+00:00
[IFD0]          Modify Date                     : 1975:03:08 12:00:00
[ExifIFD]       Date/Time Original              : 1975:03:08 12:00:00
[ExifIFD]       Create Date                     : 1975:03:08 12:00:00
[ExifIFD]       Offset Time Digitized           : +00:00
[Composite]     Create Date                     : 1975:03:08 12:00:00+00:00

When in doubt, you can use -AllDates in place of -DateTimeOriginal in the commands below too; it costs nothing on files that only had one date.

Setting a date and caption by hand

Prints with a note on the back get the date and the note in one pass. Immich shows XMP-dc:Description as the photo’s description, and the EXIF ImageDescription copy keeps the caption readable in other tools:

exiftool -overwrite_original -DateTimeOriginal="1987:06:01 12:00:00" -ImageDescription="Wedding, Nairobi, June 1987" -XMP-dc:Description="Wedding, Nairobi, June 1987" wedding-1987.jpg

One file changes.

    1 image files updated

Check the caption made it into the file with exiftool -ImageDescription -Description wedding-1987.jpg before you move on, because a typo here becomes the description everyone sees in Immich.

Fixing a whole roll with a date and place

Prints from one roll of film share a day and a place, so you can date them per folder. Pass the folder instead of a file, and add GPS coordinates if you know where the roll was shot:

exiftool -overwrite_original -DateTimeOriginal="1995:08:12 10:00:00" -CreateDate="1995:08:12 10:00:00" -GPSLatitude=-4.0435 -GPSLatitudeRef=S -GPSLongitude=39.6682 -GPSLongitudeRef=E beach-1995/

All three files in the folder are updated.

    1 directories scanned
    3 image files updated

Those coordinates do more than put a pin on the map. Immich works out the timezone from them, so this roll kept its 10:00 local time on upload, while files with no location and no offset tags are read as UTC.

Leaving the original untouched with an XMP sidecar

If the scans are archive masters you don’t want to rewrite, put the date in an XMP sidecar next to the file instead. Immich prefers the photo.jpg.xmp name, per its sidecar docs:

exiftool -o scan-0004.jpg.xmp -XMP-exif:DateTimeOriginal="1983:11:20 12:00:00" -XMP-dc:Description="Family reunion 1983" scan-0004.jpg

exiftool reports a new file rather than an update, and scan-0004.jpg keeps its original bytes:

    1 image files created

During the upload the Immich CLI found the sidecar on its own, and Immich used its exif:DateTimeOriginal as the photo’s date, which is the tag the sidecar docs list. A second test sidecar using photoshop:DateCreated also worked, but that tag is not in the documented read list, so stick to the EXIF one.

If you only need a rough date for one stray file, setting the modification time with touch -d "1990-05-05 12:00" nodate.jpg also worked, because that’s the fallback Immich uses, but any copy that doesn’t preserve timestamps loses it, so we wouldn’t rely on it for a whole archive.

Uploading scans to Immich with the CLI

For a handful of files the web uploader works, but the CLI is the better tool for a scanning project. It recurses through folders, turns folder names into albums, uploads sidecars, and skips files the server already has, because it hashes each file first. It needs Node.js, which Ubuntu 26.04 ships in its own repositories. If Immich isn’t running yet, follow the install guide linked at the top first; it runs on Docker Compose.

sudo apt install -y nodejs npm

Install the CLI globally from npm.

sudo npm i -g @immich/cli

Confirm the version matches your server.

immich --version

The CLI and the server we tested were on the same release:

3.3.5

Create an API key in the Immich web UI from your avatar, then Account Settings, API Keys and New API Key. We granted it all permissions; a key limited to fewer permissions can fail on album creation, search or asset updates. Then log in with it. We ran the CLI on the Immich host, hence localhost; from a laptop, use the server’s address such as http://10.0.1.50:2283/api:

immich login http://localhost:2283/api your-api-key

The login is saved for later commands.

Logging in to http://localhost:2283/api
Logged in as [email protected]
Wrote auth info to /home/mary/.config/immich/auth.yml

Run a dry run from inside the scans folder first. It shows how many files and albums the real run would create without touching the server:

immich upload --recursive --album --no-progress --dry-run .

It reports eleven scans and two albums, one per folder.

Crawling for assets...
Received 11 files (648.5 kB), hashing...
Found 11 new files and 0 duplicates
Would have uploaded 11 assets (648.5 kB)
Would have created 2 new albums
Would have updated albums of 11 assets
Would have deleted 0 local assets

One surprise came after the real run: the files in the top folder went into an album named after the folder itself, scans, which you can see in the sidebar of the timeline screenshot below. If you’d rather name the album, use --album-name "Family prints" instead of --album. Drop --dry-run for the real upload:

immich upload --recursive --album --no-progress .

The upload confirms the files and albums.

Crawling for assets...
Received 11 files (648.5 kB), hashing...
Found 11 new files and 0 duplicates
Uploading 11 assets (648.5 kB)
Successfully uploaded 11 new assets (648.5 kB)
Successfully created 2 new albums
Successfully updated 11 assets

There is also a --delete flag, which removes local files after upload. Leave it off for scans, because the folder on your disk is the master copy.

Checking the dates Immich chose

Ask the API for every asset’s date, description and city (this returns the first 100 assets, so page through larger imports), so you can spot misdated scans before they scatter through the timeline. Put your API key in a variable first:

KEY=your-api-key

Then pull the metadata and print one line per file.

curl -s -X POST http://localhost:2283/api/search/metadata -H "x-api-key: $KEY" -H 'Content-Type: application/json' -d '{"size":100,"withExif":true}' > /tmp/assets.json
jq -r '.assets.items[] | [.originalFileName,.fileCreatedAt,.localDateTime,.exifInfo.dateTimeOriginal,.exifInfo.description,.exifInfo.city,.exifInfo.country] | @tsv' /tmp/assets.json | sort | column -t -s$'\t'

Every fixed file landed on its real date, and the two unfixed scans didn’t:

1992-12-25_christmas.jpg  1992-12-25T12:00:00.000Z  1992-12-25T12:00:00.000Z  1992-12-25T12:00:00+00:00
beach-01.jpg              1995-08-12T07:00:00.000Z  1995-08-12T10:00:00.000Z  1995-08-12T07:00:00+00:00                                  Mombasa  Kenya
beach-02.jpg              1995-08-12T07:00:00.000Z  1995-08-12T10:00:00.000Z  1995-08-12T07:00:00+00:00                                  Mombasa  Kenya
beach-03.jpg              1995-08-12T07:00:00.000Z  1995-08-12T10:00:00.000Z  1995-08-12T07:00:00+00:00                                  Mombasa  Kenya
graduation-2001.jpg       2001-07-01T12:00:00.000Z  2001-07-01T12:00:00.000Z  2001-07-01T12:00:00+00:00
nodate.jpg                1990-05-05T12:00:00.000Z  1990-05-05T12:00:00.000Z  1990-05-05T12:00:00+00:00
scan-0001.jpg             2026-10-26T18:29:51.020Z  2026-10-26T18:29:51.020Z  2026-10-26T18:29:51.02+00:00
scan-0002.jpg             2026-10-26T18:30:01.000Z  2026-10-26T18:30:01.000Z  2026-10-26T18:30:01+00:00
scan-0003.jpg             1979-04-01T12:00:00.000Z  1979-04-01T12:00:00.000Z  1979-04-01T12:00:00+00:00     Easter 1979, Kisumu
scan-0004.jpg             1983-11-20T12:00:00.000Z  1983-11-20T12:00:00.000Z  1983-11-20T12:00:00+00:00     Family reunion 1983
wedding-1987.jpg          1987-06-01T12:00:00.000Z  1987-06-01T12:00:00.000Z  1987-06-01T12:00:00+00:00     Wedding, Nairobi, June 1987

Read it row by row. scan-0001.jpg had no EXIF date and took its file time, and scan-0002.jpg kept its scan-time stamp, so both sit in October 2026. The two sidecar scans picked up their dates and captions without their JPEGs being touched, and the beach roll shows the timezone at work: 07:00 UTC, stored as 10:00 local time because the coordinates put it in Kenya. In the web timeline, the fixed prints fall into their own years:

Immich timeline showing scanned old photos placed in 1987, 1990, 1992 and 1995

On a big import, the year scrubber on the right is the quickest check. A pile of photos under the current year means a batch went up without dates.

immich_machine_learning keeps restarting with a NumPy X86_V2 error

Our first Immich start on a Proxmox VM left the machine learning container in a restart loop while the server, Postgres and Redis came up healthy. Its log ended with:

RuntimeError: NumPy was built with baseline optimizations:
(X86_V2) but your machine doesn't support:
(X86_V2).

Our VM used the kvm64 CPU type, which shows up as Common KVM processor and hides the x86-64-v2 instructions that Immich’s machine learning image now requires. VMs created in the current Proxmox web UI default to x86-64-v2-AES and don’t hit this; older or CLI-made ones can. Uploads and dates still work without it, but face detection and smart search do not. We switched the VM to the host CPU type on the Proxmox host, replacing 101 with your VM’s ID:

qm set 101 --cpu host

On a cluster, --cpu x86-64-v2-AES is the safer choice, because host can break live migration to a node with a different CPU. Either way, restart the VM from Proxmox with qm reboot 101 or a stop and start so QEMU picks up the new CPU, since a reboot inside the guest keeps the old one. After that all four containers report healthy:

NAME                      STATUS
immich_machine_learning   Up 55 seconds (healthy)
immich_postgres           Up 55 seconds (healthy)
immich_redis              Up 55 seconds (healthy)
immich_server             Up 55 seconds (healthy)

On a NAS rather than a VM, this is a CPU question, which is why the NAS picks for Immich only include x86-64-v2 chips.

Correcting dates and places inside Immich

Some prints only get dated after a relative looks at them, and by then they’re already in Immich. Open the photo, open the info panel, and click the pencil next to the date. The dialog takes a date, a time and a timezone:

Immich edit date and time dialog used to fix the date of a scanned photo

For a batch, select the photos in the timeline, open the three-dot menu and choose Change date, or call the API. This sets the no-EXIF scan from our test to March 1984, looking up its asset ID by file name first. Scanner names like scan-0001.jpg repeat across batches, so check the search returns one match before you trust items[0]:

ID=$(curl -s -X POST http://localhost:2283/api/search/metadata -H "x-api-key: $KEY" -H 'Content-Type: application/json' -d '{"originalFileName":"scan-0001.jpg"}' | jq -r '.assets.items[0].id')
curl -s -i -X PUT http://localhost:2283/api/assets -H "x-api-key: $KEY" -H 'Content-Type: application/json' -d "{\"ids\":[\"$ID\"],\"dateTimeOriginal\":\"1984-03-10T12:00:00.000Z\"}"

A 204 No Content means it was accepted. Read the asset back to confirm the new date:

curl -s http://localhost:2283/api/assets/$ID -H "x-api-key: $KEY" | jq '{originalFileName,fileCreatedAt,localDateTime,exifInfo:{dateTimeOriginal:.exifInfo.dateTimeOriginal,timeZone:.exifInfo.timeZone}}'

The asset moved within seconds:

{
  "originalFileName": "scan-0001.jpg",
  "fileCreatedAt": "1984-03-10T12:00:00.000Z",
  "localDateTime": "1984-03-10T12:00:00.000Z",
  "exifInfo": {
    "dateTimeOriginal": "1984-03-10T12:00:00+00:00",
    "timeZone": "UTC+0"
  }
}

Immich did not touch the JPEG. The FAQ says edits go to the database and a sidecar, and the sidecar docs only promise a write after description, rating or tag edits, so we checked the upload folder after the date change. Our UPLOAD_LOCATION was the default ./library inside ~/immich-app; swap in your own path:

sudo find ~/immich-app/library/upload -name '*.jpg.xmp' -exec cat {} \;

A new sidecar sat next to the stored original, holding the corrected date in two tags:

<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 13.59'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>

 <rdf:Description rdf:about=''
  xmlns:exif='http://ns.adobe.com/exif/1.0/'>
  <exif:DateTimeOriginal>1984-03-10T12:00:00.000Z</exif:DateTimeOriginal>
 </rdf:Description>

 <rdf:Description rdf:about=''
  xmlns:photoshop='http://ns.adobe.com/photoshop/1.0/'>
  <photoshop:DateCreated>1984-03-10T12:00:00.000Z</photoshop:DateCreated>
 </rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='w'?>

That sidecar is what keeps the fix if you ever rebuild the database from the files, so back it up with the photos.

Adding a location moved the time by three hours

One thing went wrong in our run, and it was an order problem. The wedding print was dated 12:00 with no location, so Immich read the time as UTC. Adding Nairobi afterwards through the API made Immich convert that stored UTC time into Kenyan time. These are the location change and the read-back, using the asset list saved earlier; we waited about 5 seconds between the two calls so the metadata job could finish:

ID=$(jq -r '.assets.items[] | select(.originalFileName=="wedding-1987.jpg") | .id' /tmp/assets.json)
curl -s -i -X PUT http://localhost:2283/api/assets -H "x-api-key: $KEY" -H 'Content-Type: application/json' -d "{\"ids\":[\"$ID\"],\"latitude\":-1.2921,\"longitude\":36.8219}" | head -1
curl -s http://localhost:2283/api/assets/$ID -H "x-api-key: $KEY" | jq '{originalFileName,localDateTime,exifInfo:{description:.exifInfo.description,city:.exifInfo.city,country:.exifInfo.country,latitude:.exifInfo.latitude,longitude:.exifInfo.longitude}}'

The PUT returns HTTP/1.1 204 No Content, and the read-back shows the local time moved from 12:00 to 15:00:

{
  "originalFileName": "wedding-1987.jpg",
  "localDateTime": "1987-06-01T15:00:00.000Z",
  "exifInfo": {
    "description": "Wedding, Nairobi, June 1987",
    "city": "Nairobi",
    "country": "Kenya",
    "latitude": -1.2921,
    "longitude": 36.8219
  }
}

Open the photo and the info panel now shows it at 3:00 PM GMT+03:00, three hours after the noon we wrote:

Immich info panel showing a scanned photo date, description and Nairobi location

For a print with only a known day, three hours is harmless. It does matter near midnight, where the same shift moves a photo to the next day. Set the location before the date, put the GPS tags in with exiftool as we did for the beach roll, or check the time once the place is added. The location edit also wrote a sidecar for that photo with the new GPS tags, another reason the sidecars belong in your backups.

Keeping the scanned archive safe

Once the import is done, the scans exist in three places that can drift apart: the folder you scanned into, Immich’s upload directory, and Immich’s database. Immich never rewrites originals, so the date and caption fixes made inside it live only in the database and the sidecars. Immich’s backup guide says to back up the whole upload location, and these are the pieces you can’t rebuild. The upload location also holds the library folder Immich uses when the storage template is on:

  • the original scans folder, with its exiftool-dated files and sidecars
  • the whole UPLOAD_LOCATION, sidecars included
  • the database dumps Immich writes to the backups folder there
  • the prints themselves, boxed and labelled with the folder name

A scanned archive is small next to a phone library, since our whole test library, thumbnails included, came to 1.6 MB and a 600 dpi scan of a 4×6 print is typically a few megabytes, so an off-site copy costs little. Borg, restic or Kopia will handle it nightly, and keeping the server’s disks healthy is covered in the NAS alternatives guide. If you’re still choosing between Immich and a general file sync tool for the archive, our comparison of Immich with Nextcloud and Seafile shows where each fits.

Keep the shoebox after scanning. The back of a print often holds a date nobody thought to type in, and a decade from now someone will want to check it.

Keep reading

Configure Samba File Share on Debian 13 / 12 Debian Configure Samba File Share on Debian 13 / 12 Backup and Restore Linux Systems with Timeshift Debian Backup and Restore Linux Systems with Timeshift Best NAS Hard Drives for 2026: WD Red vs Seagate IronWolf, Price per TB Storage Best NAS Hard Drives for 2026: WD Red vs Seagate IronWolf, Price per TB Nextcloud vs ownCloud vs Seafile vs Syncthing vs OpenCloud Storage Nextcloud vs ownCloud vs Seafile vs Syncthing vs OpenCloud Best Synology NAS for Home, Plex and Backups: DS225+ vs DS425+ vs DS925+ vs DS1525+ Storage Best Synology NAS for Home, Plex and Backups: DS225+ vs DS425+ vs DS925+ vs DS1525+ LPIC 101 – Managing Partitions and Filesystems on Linux Storage LPIC 101 – Managing Partitions and Filesystems on Linux

Leave a Comment

Press ESC to close