← Projects

DIY NAS

TimelineMarch 2026
TrueNASSelf-hostingHomelab
HP EliteDesk 800 G3 SFF NAS build

Why I Built This

I was paying 23 HKD/month for iCloud+ and 80 HKD/month for Framer. 103 HKD a month for storage and a website I could run myself. I also didn’t really want my photos living on Apple’s servers indefinitely, so I started looking into building a NAS.

Online benchmarks show the EliteDesk at around 16.7W idle with 2 HDDs, 29W under load. Mine has 3 HDDs and runs Immich, Nextcloud, and a few other things on top, so probably somewhere between 25 to 40W. I haven’t put a meter on it yet. At HK rates of ~1.4 HKD/kWh that’s somewhere around 25 to 40 HKD/month in electricity, netting roughly 60 to 80 HKD/month against the ~103 HKD saved. I wasn’t doing this purely for the savings anyway.

Hardware

Budget was around 2,400 HKD (~$310 USD). I went with the HP EliteDesk 800 G3 SFF, picked it up on Carousell for 850 HKD. The main reason I picked this over something like a Dell Optiplex SFF is that the EliteDesk fits 2 x 3.5" HDDs natively. Most other SFFs don’t, you’d need at least a tower for that. Compact, low power, and the used market for it in HK is decent.

HP EliteDesk 800 G3 SFF

The SFF has 3 SATA ports and an M.2 2280 NVMe slot. I used the NVMe for the OS so all 3 SATA ports could go to HDDs. Got a Samsung PM991 256GB from Carousell for 200 HKD. Worth noting: the PM991 comes in multiple form factors — 2230, 2242, and 2280 at minimum. The EliteDesk needs 2280, so make sure you’re getting the right variant.

For storage, 3x 4TB HDDs (mix of new and used) for 1,300 HKD. The chassis only has 2 x 3.5" bays natively, so for the third drive I printed a mount from MakerWorld in PETG. It’s listed for the G4/G5 but fits the G3 fine. Cables (Ethernet, 6-pin to 4x SATA power, SATA data) came to 51 HKD.

HP EliteDesk 800 G3 SFF (used)850 HKD
Samsung PM991 NVMe 256GB (used)200 HKD
3x 4TB HDD (mix of new/used)1,300 HKD
Cables (Ethernet, SATA power, SATA data)51 HKD
Total2,401 HKD (~$310 USD)
DIY NAS build

Software Stack

TrueNAS Scale for the OS. Most apps install through the Apps UI, Nginx I ran as a Docker container from the shell since there’s no native app for it.

AppPurpose
TrueNAS ScaleNAS OS
TailscaleRemote access VPN
ImmichPhoto/video backup — replaces Google Photos & iCloud
NextcloudFile sync — replaces Google Drive & iCloud Drive
Calibre-WebEbook library
HomepageService dashboard for all apps
ScrutinyHDD S.M.A.R.T health monitoring
UmamiSelf-hosted analytics, replaces Google Analytics
Nginx (Docker)Serve this website
Cloudflare TunnelPublic access to latishab.com

3x 4TB in RAIDZ1 gives about 7.28 TiB usable with one-drive fault tolerance. Boot drive is separate from the data pool.

Installing TrueNAS

Flashed the TrueNAS Scale ISO to a USB drive with Balena Etcher (ARM64 build on my M4 Mac). Boot menu on the EliteDesk is ESC then F9, not F10 (that’s BIOS setup). First thing I hit was a “bad shim” error. Had to disable Secure Boot to get past it.

The USB didn’t show up in the boot menu, so I used Boot From File and navigated to EFI > debian > bootx64.efi manually. Once in the installer it found the PM991 and I pointed the install at it.

TrueNAS dashboard after installation

The University Network Nightmare

Our campus network uses 802.1X and blocks device-to-device communication. My Mac and the EliteDesk were on the same network and I still couldn’t reach the TrueNAS web UI. Ping timeout every time.

The EliteDesk doesn’t have a WiFi card, so Ethernet is the only option. I ended up plugging a cable directly between the Mac and the EliteDesk. They negotiate a link-local address (169.254.x.x) and the web UI comes up that way. To get the NAS online during setup I ran phone hotspot > Mac internet sharing > Ethernet to EliteDesk. Annoying, but it works.

Cloudflare Tunnel was a separate fight. The campus firewall blocks port 7844 on both TCP and UDP, which is what Cloudflared uses. QUIC occasionally gets through via Hong Kong edge servers but it’s degraded. About 1 in 4 connections works. Not usable for anything that needs to actually stay up.

Tailscale fixed the remote access problem. Installed it through the TrueNAS Apps UI and now I can reach the NAS from anywhere. It’s on basically every device I own at this point.

Setting Up Each App

Immich installs through the Apps UI. Auto-backup from my phone worked immediately. The “Free Up Space” feature deletes photos from your phone that are already backed up, which is the main thing I wanted.

Nextcloud threw a trusted domain error when I accessed it via Tailscale IP. You have to add the IP to the trusted_domains array in the config file.

Calibre-Web needs a metadata.db from the Calibre desktop app before it does anything. You can’t just point it at a folder of EPUBs. The default storage type in the TrueNAS app config uses an ixVolume, which dumps the books directory into a hidden internal volume. Change it to a Host Path pointing to a dataset you create on your pool, set up an SMB share on that dataset, then copy the metadata.db and book files over from your Mac.

Homepage is a dashboard that puts all my services in one place: Immich, Nextcloud, Calibre-Web, Scrutiny, Umami, and the TrueNAS UI are all accessible from a single page. It pulls in live widgets (storage usage, photo count, server stats) so I don’t have to open each app individually to see what’s going on.

Homepage dashboard showing all self-hosted services

Scrutiny monitors the S.M.A.R.T health of all drives and surfaces the data in a clean UI. More on what it flagged below.

Umami is a self-hosted analytics platform, basically Google Analytics but on my own server. I use it to track visits on latishab.com without sending data to Google.

HDD Health

Scrutiny flagged /dev/sda (the Seagate ST4000VN000) as failed. Running smartctl -a /dev/sda shows why: two UNC (uncorrectable read) errors at LBA 0x0fffffff, both logged at 11,250 hours power-on lifetime (468 days + 18 hours).

Scrutiny dashboard showing drive health status

The current Power_On_Hours counter reads 1,542 -- about 63 days -- which is lower than the 11,250 hours when the errors were logged. That’s only possible if the SMART counter was reset at some point, likely by a previous owner before listing it on Carousell. The error log is harder to wipe than the hour counter, so the errors survived. The drive’s actual age is unknown.

The full SMART report still shows PASSED on the overall health assessment. Reallocated sectors are at 0, pending sectors are at 0, and an extended self-test completed without error at 1,521 hours. The raw UNC count is 2 and hasn’t moved since I’ve had the drive. It’s in RAIDZ1 with two other drives, so one failure is survivable. I’m watching it, but not replacing it yet.

Self-Hosting My Website

This site used to be on Framer at 80 HKD/month. Part of why I wanted to move off it was blogging: Framer only gives you 1 CMS collection on the basic plan, so adding a blog would have meant upgrading to a more expensive plan. That was annoying enough to be the final push. I rebuilt the site in Next.js as a static export, serve it from an Nginx Docker container on port 8080, and expose it through Cloudflare Tunnel. DNS moved from Framer to Cloudflare; the domain is on Hostinger, which lets you set custom nameservers.

Lessons Learned

I spent a while trying to make Cloudflare Tunnel work before realizing port 7844 is just blocked on campus. Tailscale solved the remote access problem, and at this point it’s on every device I own.

RAIDZ1 is fault tolerance, not a backup. It doesn’t protect against accidental deletion or anything that isn’t a drive failure. That gap is what pushed me to think more carefully about used drives too. If you’re buying them, run Scrutiny or smartctl immediately. One of mine came with a reset SMART hour counter. The errors from the previous owner survived the reset and only showed up in the error log. I wouldn’t have known without checking.

Scrutiny marking a drive as failed looks scarier than it is. The third HDD flagged higher temps running vertically and Scrutiny marked it failed, but switching to horizontal didn’t change the error count. The errors were pre-existing from a previous owner and had nothing to do with orientation.

Calibre-Web won’t do anything without a metadata.db from the Calibre desktop app. The default storage type in the TrueNAS app config uses an ixVolume, which means the books directory ends up in a hidden internal volume you can’t easily access. Change it to a Host Path pointing to a dataset you create on your pool, set up an SMB share on that dataset, then copy the metadata.db and book files over from your Mac.

What's Next

The dorm had a power outage once and I got through it fine, but I’m sitting on a 3.5-day uptime streak now and it made me think. I’m also graduating soon and moving to a new apartment, so having a UPS becomes more important when I can’t rely on someone else’s building infrastructure. Immich also crashed the server once under load since face recognition and thumbnail generation push RAM hard. I’m on 8GB right now and might bump it to 16GB. The EliteDesk takes standard DDR4 DIMMs so it’s a straightforward upgrade.

On the backup side, I want to sort out offsite and prioritize what actually gets backed up first. Photos and videos, then everything else.

I also want to add a 2.5G or 10G NIC at some point. Still need to confirm a half-bracket card clears the third HDD mount before buying anything.