Handmade NAS


April 26, 2013


What you will learn…

  • FreeBSD setup with ZFS as root filesystem
  • Basic file sharing with smb, afp and ftp server
  • Use these as building blocks for a full fledge NAS

What you should know…

  • FreeBSD installation
  • Network configuration
  • Port installation & configuration


In the past, the term NAS (Network Attached Storage) was used to be associated with large expensive network and enterprise grade luxurious fileserver. At that time, who would have thought that hard disk and RAM becomes a common commodity that usual folks like us will posses.

With the well known FreeNAS distribution, building a multi terabytes NAS seems to be trivial affair. But today, we’re going to talk about how to build a NAS by hand, plugging different components together using command line & FreeBSD. This article does not aims to be a comprehensive guide to build a NAS. Rather, serves as a jumpstart guide on what are the basic components that made up of a NAS. Which then the reader can plug in more components to enrich its features.

The components of the NAS we’re going to build, involves:

  • FreeBSD server – setup with ZFS as root filesystem using mirroring configuration
  • Samba – file server targeting windows client
  • AFP through Netatalk – file server (with time machine) targeting mac
  • FTP server – generic file sharing server
  • pf – packet filtering as firewall

Scenario requirements for this guide:

  • modern INTEL / AMD processor
  • at least 2gb of ram
  • at least 2 hard disk
  • megabit NIC or better


This guide is a walkthrough on setting up a NAS and it WILL destroy your existing data on the hard disk. You have been warned.

FreeBSD server setup

As of this writing, the FreeBSD default installer does not support ZFS filesystem layout. As such, we’ll have to hand craft the ZFS layout during the installation of FreeBSD 9.1 server. Which would allow us to learn more about the underlying works of FreeBSD root on ZFS filesystem.

First of all, get the installation CD from http://freebsd.org and boot it. Perform the installation as usual until the menu “Partitioning”, choose “Shell”.

After dropping to the shell, execute the following commands:

Listing 1a. FreeBSD root on ZFS

# gpart create -s gpt da0

# gpart add -t freebsd-boot -s 128 -l primary-boot da0

# gpart add -t freebsd-swap -l primary-swap -s 4g da0

# gpart add -t freebsd-zfs -l primary-root da0

# gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0

# gpart create -s gpt da1

# gpart add -t freebsd-boot -s 128 -l secondary-boot da1

# gpart add -t freebsd-swap -l secondary-swap -s 4g da1

# gpart add -t freebsd-zfs -l secondary-root da1

# gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da1

# zpool create -f -m none -o altroot=/mnt -o cachefile=/tmp/zpool.cache zeetank mirror gpt/primary-root gpt/secondary-root

# zfs create -o mountpoint=/ zeetank/root

# zpool set bootfs=zeetank/root zeetank

# zfs list

# zpool get all zeetank

Type “exit” at the command prompt once the above is done. The installation should continue, extracting the necessary files to the hard disk.

What the above has done is to create a GPT partition table in the very first step. Then follow by creating a partition of size 128 Kilobytes for boot partition and a size of 4 Gigabytes for swap partition (based on 2GB ram). The rest of the hard disk space is allocated to the data area, root partition.

After the partitioning of the hard disk is done, initialize the boot area with boot code found in /boot/gptzfsboot.

The same disk layout is duplicated to the second hard disk, in order to create ZFS mirroring disk setup.

Next, create ZFS pool with the name of “zeetank”, consisting the partition “primary-root” & ”secondary-root” with ZFS mirroring. Then create a mount point to hold the root partitions and set the boot partitions to find “zeetank/root” for necessary booting files. Finally, list the disk layout we just did and double confirm it.

If typo or any misconfiguration of the disk layout happened, use the following commands for disk partition to delete and/or destroy:

# gpart delete -i 1 da0

# gpart destroy da0

The completed disk layout should look like this:

Listing 1b. Sample of partition layout

root@handmade-nas:/root # gpart show

=> 34 20971453 da0 GPT (10G)

34 128 1 freebsd-boot (64k)

162 1048576 2 freebsd-swap (512M)

1048738 19922749 3 freebsd-zfs (9.5G)

=> 34 20971453 da1 GPT (10G)

34 128 1 freebsd-boot (64k)

162 1048576 2 freebsd-swap (512M)

1048738 19922749 3 freebsd-zfs (9.5G)

Listing 1c. Sample of ZFS setup

root@handmade-nas:/root # zpool get all zeetank


zeetank size 9.44G –

zeetank capacity 0% –

zeetank altroot /mnt local

zeetank health ONLINE –

zeetank guid 7047189317064599296 default

zeetank version 28 default

zeetank bootfs zeetank/root local

zeetank delegation on default

zeetank autoreplace off default

zeetank cachefile /tmp/zpool.cache local

zeetank failmode wait default

zeetank listsnapshots off default

zeetank autoexpand off default

zeetank dedupditto 0 default

zeetank dedupratio 1.00x –

zeetank free 9.44G –

zeetank allocated 152K –

zeetank readonly off –

zeetank comment – default

zeetank expandsize 0 –

For more disk redundancy setup on ZFS, you should refer to the FreeBSD Handbook. The ZFS Administration Guide from solaris project can also serve as a good reference. (see Reference for links)

The installation should resume after “exit“. Proceed as usual on how you would install FreeBSD typically. For example, set password for root, configure network interface, setting up time zone and etc. When the menu “Manual Configuration” appears, choose “No”. On the next menu, “Complete”, choose “Live CD” instead, because there is still couple of files we need to setup. Do take note that the root partitions that we created earlier, are mounted on /mnt.

Log in as root (it should not prompt for password in ”Live CD” mode) and execute the code from Listing 1d.

Listing 1d. xxxxxxxxx

# echo ‘zfs_enable=”YES”‘ >> /mnt/etc/rc.conf

# echo ‘zfs_load=”YES”‘ >> /mnt/boot/loader.conf

# echo ‘vfs.root.mountfrom=”zfs:zeetank/root”‘ >> /mnt/boot/loader.conf

# echo ‘/dev/da0p2 none swap sw 0 0’ >> /mnt/etc/fstab

# echo ‘/dev/da1p2 none swap sw 0 0’ >> /mnt/etc/fstab

The lines with “echo” are the necessary ZFS startup parameters FreeBSD needs to know. Next, copy the ZFS cache file onto the mounted file system in order for ZFS to boot properly: see Listing 1e.

Listing 1e. xxxxxxxxxxxx

# zpool export zeetank

# zpool import -o altroot=/mnt -o cachefile=/tmp/zpool.cache zeetank

# cp /tmp/zpool.cache /mnt/boot/zfs/

List the ZFS properties to make a visualize check. Reboot when finish:

# zpool get all zeetank

# reboot

After reboot, log into the box and make yourself as root. Fetch and extract the ports tree as necessary.

Then create a user named “bob”, so that we can log into the file sharing services that we’re going to setup. Since this user is just a common user accessing files through various services, we should stop it from login into the system through any shell. This will be covered in the section setting up FTP server later. For now, create the user:

# pw useradd -mn bob

# passwd bob

Samba File Server

The samba server that we’re going to configure, is meant for windows client file sharing. Firstly, install the port “net/samba36”. For example,

# make -C /usr/ports/net/samba36 install clean

The samba authentication method we are going to use stores information in trivial database. The password files holding user credentials are passdb.tdb & secrets.tdb, resides in /usr/local/etc/samba/.

Edit the file /usr/local/etc/smb.conf with the contents showed in Listing 2a.

Listing 2a. xxxxxxxxxx


workgroup = Private

netbios name = NAS_box_smb

security = user

encrypt passwords = yes

client lanman auth = no

log file = /var/log/samba/log.%m

passdb backend = tdbsam

load printers = no

printcap name = /dev/null


path = /home/bob

browseable = no

writeable = yes

valid users = bob

admin users = bob

Add a user to samba trivial database:

# pdbedit -au bob

Next, start Samba services:

# echo ‘samba_enable=”YES”‘ >> /etc/rc.conf

# service samba start

If all is well, windows client can access the share named “bob” on this box.

AFP Through Netatalk

Mac can share files and backup using time machine with AFP (Apple Filing Protocol), through Netatalk file sharing service. Before starting, install the port “net/netatalk” and uncheck the option “ZEROCONF”. Unless you want to use the Bonjour function through zeroconf.

There will be a few files we will need create/edit:

  • /usr/local/etc/AppleVolumes.default
  • /usr/local/etc/afpd.conf
  • /usr/local/etc/netatalk.conf
  • /etc/rc.conf

Create the file /usr/local/etc/AppleVolumes.default with the following contents:

: DEFAULT : options:upriv,usedots

/home/bob “bob’s home directory” allow:bob options:tm

The file AppleVolumes.default tells netatalk that the share “/home/bob” can be share only with user “bob” and “Time Machine” function is available with the share.

Next, create the file /usr/local/etc/afpd.conf with these contents, which is only a SINGLE line:

– -ipaddr

This basically tell afpd to listen for incoming request in all network interface. Next, create /usr/local/etc/netatalk.conf with the below contents:


The settings in netatalk.conf should be pretty self-explanatory, setting up the hostname for netatalk service.

Finally, set rc parameters in /etc/rc.conf:

# echo ‘afpd_enable=”YES”‘ >> /etc/rc.conf

# echo ‘atalkd_enable=”NO”‘ >> /etc/rc.conf

# echo ‘cnid_metad_enable=”YES”‘ >> /etc/rc.conf

# echo ‘netatalk_enable=”YES”‘ >> /etc/rc.conf

That’s all for the service netatalk. Start the services by:

# service netatalk start

Remember to check /var/log/messages for error messages. The Mac clients should be able to browse this server for appletalk shares as well as using this share for time machine backups if there is not error messages logged.

FTP (File Transfer Protocol)

The FTP server we are going configure is to provide general file upload & sharing. We will be using the ftp daemon that comes with FreeBSD base installation, ftpd. We’ll also restrict ftp user login to it’s home directory, effectively chroot the user.

In the creation of user bob earlier, the default shell assigned is /bin/sh, which allow the user login using a shell. In order to restrict the user login through services we have configured (samba, netatalk and ftp), we should disable it’s login capabilities. For this purpose, do the following:

Listing 4. xxxxxx

# cp /sbin/nologin /usr/local/bin/ftp-login-only

# echo “/usr/local/bin/ftp-login-only” >> /etc/shells

# mkdir /home/bob/ftpdir

# pw usermod -n bob -d “/home/bob/ftpdir/./” -s “/usr/local/bin/ftp-login-only”

# echo “bob” >> /etc/ftpchroot

The above will duplicate a copy of the nologin shell from base, and list it in /etc/shells, so that ftpd would recognize it. Then, change ”bob” user profile to use the shell duplicated above and set it as home directory. Do take note of the separator “/./”, this would tell ftpd to set the “root” directory structure of the user, starting from there. Effectively preventing the user to go beyond /home/bob/ftpdir.

Next, start the ftp daemon with the following commands:

# echo ‘ftpd_enable=”YES”‘ >> /etc/rc.conf

# service ftpd start

The ftp daemon will log its messages to /var/log/xferlog by default.

Packet Filter – pf

pf is a OpenBSD firewall that is ported to the base of FreeBSD since version 5.x. It is well known for its feature, performance & syntax simplicity. For these reason, we will use it to add another layer of security to the services configure above. Put the following into /etc/pf.conf:

Listing 5. /etc/pf.conf

services_tcp=”{ 21, 22, 139, 445, 548 }”

services_udp=”{ 137, 138 }”

clients_ip=”{, }”

block in all

block out all

pass quick on lo0 all

pass in inet proto icmp all icmp-type echoreq

pass in proto tcp from $clients_ip to port $services_tcp

pass in proto udp from $clients_ip to port $services_udp

# for ftpd to work properly

pass in proto tcp from $clients_ip to port > 49151

The pf rules above allows the IP in “$clients_ip” accessing the services listed in $services_tcp and $services_udp. It also allow ping from all IPs, for troubleshooting purposes. Loopback interface (lo0) checked will be skipped and the default policy is to “block” both inbound & outbound traffic, if no rules are matched.

Next, start up packet firewall:

# echo ‘pf_enable=”YES”‘ >> /etc/rc.conf

# service pf start


This wraps up the guide on how to setup a NAS by hand configuring the services. This guide with the configuration above are bare minimum. Its purpose is to allow anyone that is interested to find out how to setup a NAS by poking around the system. More reading should be done and, care taken onto securing the services. Of cause, the configuration above could certainly be alter to provide more features. For an example, Samba and Netatalk users can be authenticated with LDAP as a backend. The feature rich ZFS filesystem are barely touch. Just to mention a few, various disk redundancy (mirror, striping with parity) configurations, file compression and filesystem snapshot. The ftp daemon is capable of virtual host like hosting. And pf, a full fledge feature rich firewall. There so much to talk about.

I hope this guide serves a good purpose providing a picture on how a NAS can be built. Below are some links for reference. Have fun.



Edward Tan’s day-to-day job is administrating a bunch of servers running on FreeBSD. In his free time, he blogs about techie stuff at http://psybermonkey.net, learns about Perl and thinks about how to contribute back to the FreeBSD community.

About the author

The author is a big fan of using BSD operating system both as a server & desktop. He often mess with scripting languages & configuration management tools then notes it down at http://psybermonkey.net. Occasionally, he’ll talk about his learnings in BSD conference.

Share On Social: