Troubleshooting Immich Performance Issues on Proxmox
Table of Contents (10 min read)
Troubleshooting Immich Performance Issues on Proxmox
I recently installed Immich on my Proxmox VE server to replace commercial cloud photo storage with a self-hosted solution. Based on homelab discussions and benchmarks across the community, Immich generally performs exceptionally well on older server hardware, such as a Dell PowerEdge T20 or modest quad-core enterprise workstations.
Right out of the gate, however, performance and stability became a major blocker. The issue did not manifest during idle operation or single-file test uploads. Instead, it struck the moment I connected my mobile device over local Wi-Fi and initiated a full camera roll backup.
After syncing anywhere between 20 and 50 high-resolution photos, my entire Proxmox hypervisor became completely unresponsive:
- The Proxmox Web GUI timed out and refused incoming connections.
- SSH sessions to the Proxmox host dropped immediately.
- SSH and network traffic to all neighboring LXC containers and virtual machines ceased.
- Pings to the hypervisor management IP (
192.168.x.x) returnedDestination Host Unreachable.
To all external observations, the physical host had suffered a catastrophic kernel panic or complete system crash. Every incident forced me to walk over to the rack and perform a hard physical power cycle.
The Real Culprit: The Illusion of a Host Crash
Once the host rebooted, I pulled the persistent systemd journal from the failed boot session (journalctl -b -1). The logs revealed that the hypervisor kernel never actually crashed. Proxmox, the underlying Debian base, and all guest VMs were running normally in the background.
The complete network blackout was triggered by two distinct, compounding failure mechanisms at the hardware driver and kernel memory layers.
1. Hardware Unit Hang on Intel Gigabit NIC (e1000e)
The physical onboard network interface on the Dell PowerEdge motherboard is an Intel Gigabit controller (such as the Intel I217-LM or 82579LM) managed by the standard Linux e1000e kernel driver module. During sustained multi-stream TCP transfers from the Immich mobile client, the controller choked:
Oct 13 19:24:24 home kernel: e1000e 0000:00:19.0 nic0: Detected Hardware Unit Hang:
Oct 13 19:24:24 home kernel: TDH <0x12>
Oct 13 19:24:24 home kernel: TDT <0x14>
Oct 13 19:24:24 home kernel: next_to_clean <0x12>
Oct 13 19:24:24 home kernel: next_to_use <0x14>
Oct 13 19:24:26 home kernel: e1000e 0000:00:19.0 nic0: Detected Hardware Unit Hang:
Oct 13 19:24:32 home pvestatd[1234]: storage 'photos' is not online
Modern Linux network stacks offload packet segmentation to hardware via TCP Segmentation Offload (TSO) and Generic Segmentation Offload (GSO). When Immich accepts hundreds of raw multi-megabyte photo payloads, the kernel transmits large 64KB aggregated chunks directly to the NIC.
On legacy Intel silicon handled by e1000e, rapid burst traffic causes a race condition in the transmit descriptor ring buffer. The hardware transmit unit locks up completely, dropping the physical interface (nic0). Because Proxmox bridges all guest and management traffic through vmbr0 on top of nic0, losing the physical link instantly severs network connectivity for the entire server.
2. Kernel Memory Starvation (kworker GFP_NOIO Failure)
Compounding the NIC failure, the sudden flood of network I/O combined with rapid disk caching caused emergency memory allocation deadlocks in the kernel:
Oct 11 05:17:02 home kernel: Out of memory and no killable processes...
Oct 11 05:17:02 home kernel: kworker/u16:12 invoked oom-killer: gfp_mask=0x101c0a(GFP_NOIO|__GFP_HIGHMEM|__GFP_MOVABLE|__GFP_WRITE|__GFP_HARDWALL), order=1
Linux default configuration assigns a relatively small vm.min_free_kbytes reserve. When Immich ingested bursts of high-bandwidth image data into host page cache, kernel background workers (kworker) attempted to allocate contiguous atomic memory pages (order=1).
Because user workloads (VMs, containers, ZFS/ext4 ARC, and page cache) occupied nearly all available physical RAM, the kernel had no contiguous free pages left. Because the driver allocation specified GFP_NOIO, the kernel could not initiate disk I/O to flush caches, resulting in an OOM invocation with no eligible user processes to kill.
Stack & Prerequisites
Before applying the fixes, verify your hardware, kernel, and software environment:
- Hypervisor: Proxmox VE 8.x running Debian 12 Bookworm (Linux kernel 6.x or newer).
- Physical Network Interface: Onboard Intel Gigabit NIC driven by the
e1000ekernel module (e.g., Intel I217-LM, I218, I219, or 82579LM). - Application Stack: Immich running inside Docker Compose hosted in a guest Debian VM (
deb-photos, VM 100) or an LXC container. - System Utilities:
ethtool,sysctl,iproute2, and root console or SSH access to the Proxmox host.
Step-by-Step Implementation
To restore permanent stability, I implemented a two-part host remediation strategy: disabling buggy hardware offloads on the physical NIC and expanding the kernel emergency memory watermark.
Step 1: Diagnose Prior Boot Logs
If your Proxmox server appears frozen during large file uploads, do not assume the hypervisor crashed. After cycling power, inspect the journal from the previous boot to confirm whether e1000e or memory deadlocks triggered the outage:
# Inspect the previous boot journal for e1000e unit hangs
journalctl -b -1 -k | grep -iE "e1000e.*hang"
# Check for atomic memory allocation failures
journalctl -b -1 -k | grep -iE "out of memory|kworker.*invoked oom-killer"
If you observe Detected Hardware Unit Hang referencing your physical interface (nic0 or eno1), proceed with the remediation below.
Step 2: Disable Hardware Offloads on the Physical NIC
Disabling TCP Segmentation Offload (TSO), Generic Segmentation Offload (GSO), and Generic Receive Offload (GRO) forces the Linux kernel CPU to handle packet segmentation rather than delegating it to the faulty NIC descriptor ring. The modern CPU overhead is negligible on a Gigabit network, and it completely eliminates the hardware hang.
1. Apply at Runtime for Immediate Testing
Run ethtool against the physical interface (substitute nic0 with your physical interface name, such as eno1):
# Disable hardware segmentation offloads on the physical network interface
ethtool -K nic0 tso off gso off gro off
2. Make the Offload Settings Persistent
In Proxmox VE, network interfaces are managed in /etc/network/interfaces. Add a post-up hook to the physical interface definition so that offloads are disabled whenever the interface initializes.
Open /etc/network/interfaces in your editor:
nano /etc/network/interfaces
Locate your physical NIC block (nic0 or eno1) and append the post-up directive:
# Physical Intel Gigabit interface connected to your switch
iface nic0 inet manual
# Disable hardware segmentation offload to prevent e1000e unit hangs
post-up /usr/sbin/ethtool -K nic0 tso off gso off gro off
# Proxmox standard bridge interface
auto vmbr0
iface vmbr0 inet static
address 192.168.x.x/24
gateway 192.168.x.1
bridge-ports nic0
bridge-stp off
bridge-fd 0
Important
The post-up directive must be applied to the physical interface (nic0), not to the virtual bridge (vmbr0). Virtual bridges do not possess physical hardware descriptor rings.
3. Verify Active Offload States
Confirm that the segmentation offloads are successfully turned off:
# Inspect offload status on the physical interface
ethtool -k nic0 | grep -E "tcp-segmentation|generic-segmentation|generic-receive"
Expected output:
tcp-segmentation-offload: off
generic-segmentation-offload: off
generic-receive-offload: off
Step 3: Increase Kernel Memory Watermarks (sysctl)
To prevent kernel workers from running out of contiguous pages during heavy burst I/O, increase vm.min_free_kbytes from the default (often 64MB) to 512MB (524288 KB), and reduce vm.swappiness to 10.
1. Create a Dedicated sysctl Configuration File
Create a drop-in configuration file in /etc/sysctl.d/:
# Path: /etc/sysctl.d/99-pve-memory.conf
cat <<'EOF' > /etc/sysctl.d/99-pve-memory.conf
# Reserve a 512MB contiguous page buffer to prevent kworker allocation deadlocks
vm.min_free_kbytes = 524288
# Reduce swappiness to favor retaining application memory over aggressive paging
vm.swappiness = 10
EOF
2. Apply Configuration Immediately
Reload sysctl parameters without rebooting:
# Apply new sysctl directives immediately
sysctl -p /etc/sysctl.d/99-pve-memory.conf
3. Verify Memory Watermark
Verify that the kernel accepted the new watermark:
# Check the active min_free_kbytes parameter
sysctl vm.min_free_kbytes
Output:
vm.min_free_kbytes = 524288
Step 4: Tune Immich Concurrency and Resource Allocation
In addition to host-level fixes, fine-tuning Immich job queues prevents thumbnail generation, facial recognition, and CLIP indexing from starving guest VM memory while millions of bytes are streaming over the socket.
- In the Immich web interface, log in as an administrator.
- Navigate to Administration > Settings > Job Settings.
- Under Machine Learning and Thumbnail Generation, reduce concurrency to
1or2threads if running on quad-core CPUs. - If deploying Immich via Docker Compose inside a dedicated VM or container, establish explicit memory and CPU constraints:
# Path: docker-compose.yml (Immich services snippet)
services:
immich-server:
image: ghcr.io/immich-app/immich-server:release
# Limit process memory consumption to avoid ballooning guest page cache
deploy:
resources:
limits:
cpus: '2.0'
memory: 4096M
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:release
deploy:
resources:
limits:
cpus: '2.0'
memory: 2048M
Gotchas & Edge Cases
When resolving network freezes in virtualized environments, keep the following operational caveats in mind:
1. The Virtual Bridge vs. Physical Slave Pitfall
A common mistake is placing the ethtool directive on vmbr0 in /etc/network/interfaces. Because vmbr0 is a software construct, disabling offloads on the bridge does not propagate down to the physical silicon descriptor ring on nic0. The command must run directly against the raw interface.
2. MTU Mismatches
Ensure your physical interface and bridge are configured for standard 1500 MTU. If you attempt to enable 9000 MTU jumbo frames on an Intel e1000e controller experiencing ring buffer pressure, the hardware hangs become even more frequent unless every switch and endpoint on the VLAN is meticulously tuned.
3. Alternative Kernel Module Parameters (IntMode)
On select hardware revisions (such as older 82579LM chips), Intel controllers may continue to encounter hangs due to MSI-X interrupt delivery bugs. If disabling TSO/GSO does not fully eliminate the problem on your specific board, configure the module option in /etc/modprobe.d/e1000e.conf:
# Force legacy or MSI interrupts if MSI-X causes interrupt throttling lockups
options e1000e IntMode=1,1
After modifying modprobe options, regenerate your initramfs (update-initramfs -u) and reboot.
Verification & Operational Benchmarks
To validate the remediation, I monitored kernel ring buffer events in real time while initiating an aggressive sync of over 2,500 photos and 4K video clips from my mobile client.
Real-Time Monitoring Command
While the mobile backup ran, I monitored the live Proxmox host kernel console over SSH:
# Stream live kernel messages filtered for hardware and memory alerts
dmesg -w | grep -iE "e1000e|oom|hung|page allocation"
Telemetry & Stability Comparison
| Operational Metric | Stock Default Configuration | Hardened Configuration |
|---|---|---|
| Ingress Throughput | Crashes after 20-50 photos | Steady line-rate (~940 Mbps) |
| Physical Link Status | Hardware Unit Hang (nic0 dropped) |
Continuous Link Up (0 drops) |
| Host Availability | Complete drop (Hard reset required) | 100% accessible via SSH and Web GUI |
| Kernel Worker Allocations | GFP_NOIO OOM killer triggered |
Zero page allocation errors |
| Total Ingested Media | Failed midway | 2,500+ photos synced without interruption |
Conclusion & DenverNerd Homelab Checklist
When a Proxmox hypervisor becomes unreachable during heavy network I/O, do not jump to the conclusion that the CPU panicked, RAM died, or the power supply tripped. Virtualized media applications like Immich create unique burst transfer profiles that easily expose subtle firmware and driver limitations on older onboard NICs.
Before replacing hardware or migrating workloads, apply this quick homelab checklist:
- Check persistent logs from the crashed boot:
journalctl -b -1 -k. - Disable TSO, GSO, and GRO on physical interfaces driven by
e1000e. - Reserve sufficient emergency kernel memory headroom via
vm.min_free_kbytes = 524288. - Throttle heavy background worker queues in Immich during large initial ingestions.
With these three adjustments in place, my Dell PowerEdge T20 now processes massive photo backups without dropping a single packet or locking up the hypervisor.
Discussion (0)
Community Guidelines EnforcedNo community comments yet. Be the first to share your thoughts!
Join the Community Discussion
Sign in or create an account to share observations, tuning setups, and connect with other makers.