Fixing the "Too many open files" Problem on My lumine VM, and Notes on Moving It to an LXC
Table of Contents
This is a work log of some server operations I did while talking things over with Gemini, which I had it summarize at the very end.
I cannot guarantee that any of this is correct!!!
Goal: Identify the root cause of the Too many open files in system error occurring on the lumine VM on Proxmox, and apply a permanent fix.
Part 1: The Problem and Finding the Cause
1. Overview of the Problem
On the Proxmox VM lumine (ID 100), the Too many open files in system error kept popping up during filesystem operations such as ls /mnt/data. As a result, the services running in Docker inside that VM, such as Jellyfin, Stash and Copyparty, stopped working properly.
The problem showed up right after I changed how the storage (an 8 TB HDD) was shared: from passing it straight through to the VM, to sharing it via the Proxmox host with multiple guests (the lumine VM and the nahida LXC).
2. Investigation Log (How My Thinking and My Commands Evolved)
-
Initial hypothesis: Are the applications in the VM (Jellyfin, Netdata) leaking file descriptors?
- Investigation: Ran
lsofinside thelumineVM.sudo lsof | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 10 - Result:
jellyfinandnetdataturned out to have over 10,000 files open. They rose to the top of the suspect list.
- Investigation: Ran
-
Revising the hypothesis: The per-process limit (
ulimit -n) is a very high1,048,576, so 10,000 or so wouldn’t hit it. The system-wide count (cat /proc/sys/fs/file-nr) was also a low value of around2400, which contradicted thelsofresults.- Reasoning: The error message appears inside the VM, but the real cause of the resource exhaustion might be outside the VM. I decided that VirtIO-FS, the mechanism used to share storage with the VM, looked suspicious.
-
Identifying the real culprit: On the Proxmox host (
pve1), I investigated thevirtiofsdprocess running for thelumineVM.- Investigation: On
pve1, I found the PID of thevirtiofsdprocess and checked how many files it had open.ps aux | grep virtiofsd# PID 454363 identified as the suspectls -l /proc/454363/fd | wc -l - Smoking gun: The
virtiofsdprocess was consuming about 1 million file descriptors, and had hit its process limit.
- Investigation: On
3. Conclusion on the Root Cause
When an application that scans the filesystem broadly, like Jellyfin or Netdata, runs inside the VM, its huge volume of access requests concentrates on the VirtIO-FS relay daemon (virtiofsd). This virtiofsd couldn’t keep up with the requests and exhausted its resources (file descriptors), and as a result it returned Too many open files errors to the VM side.
I concluded that this is a fundamental incompatibility between broad-scanning applications and the VirtIO-FS technology itself.
Part 2: The Fix: An Architectural Migration from VM to LXC
1. Basic Policy
To stop using VirtIO-FS entirely, I move the role of lumine from a VM to an LXC container. With LXC, the host’s filesystem can be shared via a bind mount, a fast and stable kernel-level mechanism, so a bottleneck like virtiofsd doesn’t occur.
To keep the current docker-compose.yml workflow, I chose what I’d call the “realistic, practical best move”: build a Docker environment inside a single LXC and migrate the existing setup over wholesale.
2. Migration Procedure Summary
Phase 1: Create the New LXC
In the Proxmox UI, I created a new LXC with the following specs, using a temporary hostname, lumine-new.
- ID: 103
- Hostname:
lumine-new - Resources: 2 cores, 8 GB RAM, 64 GB disk
- Template: Ubuntu 24.04
- Network: Set the same IP (
192.168.x.201/24) and MAC address (BC:24:11:xx:xx:xx) as thelumineVM. - Options: Enabled Nesting and keyctl.
Phase 2: Edit the LXC Config (on the pve1 Host)
I appended settings for storage and hardware passthrough to the new LXC’s config file (/etc/pve/lxc/103.conf).
# Bind-mount the HDD for mediamp0: /mnt/storage-8tb,mp=/mnt/data
# Intel QSV (iGPU) passthroughlxc.cgroup2.devices.allow: c 226:* rwmlxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dirPhase 3: Set Up the Inside of the LXC
- Start the LXC and log in as root.
- Create the
myuseruser with the same UID/GID (1000:1000) as on the old VM.groupadd -g 1000 myuseruseradd -m -s /bin/bash -u 1000 -g 1000 myuserpasswd myuserusermod -aG sudo myuser - Install Docker and Docker Compose.
curl -fsSL https://get.docker.com -o get-docker.shsh get-docker.shapt install -y docker-compose-pluginusermod -aG docker myuser
Phase 4: Migrate the Data
To avoid an IP address conflict, I temporarily changed the old lumine VM’s IP to 192.168.x.250 with netplan. Then I copied the data over from the new LXC with rsync.
# Run on the lumine-new LXC sidemkdir -p /home/myuser/docker/mainrsync -avz --progress myuser@192.168.x.250:~/docker/main/ /home/myuser/docker/main/Phase 5: Start the Services and Do the Final Switchover
- Run
docker-compose up -don the new LXC. - After the troubleshooting described below, confirm that all services work properly.
- Shut down the old VM and the new LXC, and swap their names in the Proxmox UI. (Old:
lumine→lumine-vm, new:lumine-new→lumine) - Start the new
lumineLXC as the production machine.
Part 3: Post-Migration Troubleshooting Log
The migration was not a straight line. Several significant problems came up, and each one needed investigating and solving.
Problem 1: AdGuard Home Port Conflict
- Symptom: When running
docker-compose up -d,adguardfailed to start with anaddress already in useerror. - Investigation:
sudo lsof -i :53showed that the host OS’ssystemd-resolvedwas using port 53. - Fix: Stopped and disabled
systemd-resolved.sudo systemctl stop systemd-resolvedsudo systemctl disable systemd-resolved - Additional problem: During AdGuard Home’s initial setup, I had set the admin port to 80, which conflicted with NPM.
- Fix: Edited
AdGuardHome.yamldirectly and changedhttp.addressto:3000.
Problem 2: The Tailscale Daemon Failed to Start
- Symptom:
sudo tailscale upfailed. Thetailscaledservice crashed withfailed (Result: exit-code). - Investigation: Checked the detailed logs with
sudo journalctl -u tailscaled.service. I found this decisive error:CreateTUN("tailscale0") failed; /dev/net/tun does not exist - Cause: The
tunkernel module wasn’t loaded on the Proxmox host, so/dev/net/tuncouldn’t be created inside the LXC. - Fix: A two-step fix.
- On the
pve1host, load thetunmodule and make it persistent.modprobe tunecho tun > /etc/modules-load.d/tun.conf - Also on the
pve1host, append the device permission and mount settings to the LXC’s config file (/etc/pve/lxc/103.conf).lxc.cgroup2.devices.allow: c 10:200 rwmlxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
- On the
Problem 3: Access via *.lumine Failed
- Symptom: Access via
IP:PORTworked, but access by hostname such asmemos.luminedidn’t. - Investigation:
ping lumineon the client PC returned the old Tailscale IP, showing that name resolution was off.- The AdGuard Home query log showed no record at all of queries from the client PC.
tcpdumpalso showed that packets from the client PC were not reaching the LXC.
- Cause: “Override DNS servers” in Tailscale’s DNS settings was enabled, forcibly overwriting the client PC’s DNS settings. Because of that, the AdGuard Home on the LAN wasn’t being used, and name resolution failed.
- Fix: In the Tailscale Admin Console > DNS settings, I did the following.
- Turn “Override DNS servers” off.
- Set AdGuard Home’s LAN IP (
192.168.x.201) as a “Global nameserver”.
Part 4: Final Check and Lessons Learned
1. Final System Configuration
The lumine services now run stably under Docker Compose inside a lightweight LXC container on Proxmox. Storage is shared quickly via a bind mount from the host, and the file descriptor problem is completely gone. On the network side, AdGuard Home and Tailscale work together, so every service can be reached by a consistent hostname from both inside and outside the LAN.
2. Lessons Learned
- The limits of VirtIO-FS: VirtIO-FS is convenient, but it is a poor fit for workloads like Jellyfin that scan the entire filesystem at high frequency. It can become a resource-exhaustion bottleneck.
- The advantages of LXC: For uses like a Docker host, LXC has less overhead than a VM and is better for performance (especially I/O). Bind mounts are very powerful.
- The importance of systematic troubleshooting: By working through the layers in order,
lsof→journalctl→tcpdump→ query log, you can eventually pin down the cause even of a complicated problem. Don’t fix things on guesswork; act on facts. - Always suspect DNS: With network problems, especially “I can’t reach it by name”, the shortcut to a solution is isolating which layer of DNS is at fault (client cache, local DNS, Tailscale MagicDNS).
3. Remaining Tasks
- [Done] Back up and delete the old
lumine-vm. - [Done] Remove the
systemdsettings that are no longer needed. - [Done] Change the new
lumineLXC’s internal hostname tolumine. - [TODO] Set up a periodic backup job in Proxmox for the new
lumineLXC.
That completes all the work on this matter.

↑Made with nano-banana. So cute.
More articles
- Noelle Reporting for Duty! (Building the AI Agent Ineffa)• #HomelabA record of how the Clawdbot-based AI agent "Ineffa (Noelle)" overcame a series of troubles and finally went live.
- Guarding the Heart, or a Lightning Flash of Admonition• #HomelabAn unsettling short story written by Gemini 3 Pro Preview.
- Noelle Has Come to My Home• #HomelabBy combining Home Assistant, the Wyoming Protocol, Style-Bert-VITS2 and more, I turned my home server into a Noelle smart speaker.
- Making Style-Bert-VITS2 Speak in Home Assistant, a Work Log• #HomelabI wrote a custom component for Home Assistant dedicated to Style-Bert-VITS2.
Notes on games, radio and technology, translated from the Japanese blog.