However, after months of service, my Raspberry Pi still runs stable. But over the time, a lot of things have changed. The original tutorial used version 2 of the controller software, while the most recent version available today is 4. With Raspbian Jessie, MongoDB became available from the repositories. The Oracle Java 8 JDK has been released. And so on. After all, I decided it was time to rewrite my tutorial and here it is.
Screenshot of the UniFi controller web interface.
I'm not providing ready-to-go images as I think it's good to know how everything is set up when problems might arise. I'll try to explain every step carefully, but just copy-pasting all commands should already be enough to yield a working controller though.
- Raspberry Pi with at least 512 MB RAM. For example: RPi 1 model B rev. 2, RPi 1 model B+ or RPi 2 model B.
- A compatible SD card. I'd recommend using a large card (16 GB) to avoid excessive write wear on it.
Necessary stepsWe'll need to take the following steps in order to get your UniFi controller up and running.
- Setup and configure the Raspbian OS
- Install the dependencies
- Install the UniFi controller
- Modify the start script in order to use Oracle Java 8 instead of OpenJDK 7
- Configure the controller to run as its own user instead of as root
- Running the controller
Raspbian setupThe first step in the process will be getting your Pi up and running with Raspbian Jessie. You can download an image on the Raspbian downloads page. I'll be using the Lite image, a minimal image wihout graphical user interface, as that's just what we need for a headless device. If you don't know how to write the image to your SD card, I'd recommend you to take a look at the wiki page on this topic. After you've flashed Raspbian to the card, it's time to boot up your Pi and log in with username pi and password raspberry. If you like, you can also log in through SSH, which is enabled out of the box.
After the image has been flashed, we still need to expand the filesystem to the full size of the SD card to be able to use all the space provided by the card. To do so, run:
Choose Expand Filesystem to adjust the filesystem size. While we're in this neat little configuration tool, you might also want to adjust the amount of RAM assigned to the graphics adapter. You can do so by choosing Advanced Options > Memory split. As we're running headless, 16MB would be more than enough. Reboot the device for the changes to take effect.
You may also want to configure the timezone you're living in, using:
sudo dpkg-reconfigure tzdata
We'll also want to make sure all packages are up to date.
sudo apt-get update && sudo apt-get upgrade
Installing the dependenciesWe will be using the unifi package from the official Debian repository of UBNT. It has two main dependencies: MongoDB and a Java Virtual Machine. This time, MongoDB is an easy one: it is available in the Raspbian repositories and will be installed automatically when the unifi package is installed.
The unifi depends on OpenJDK 7 to provide the Java Virtual Machine. That's fine on x86/amd64 platforms, but on a Raspberry Pi this would is not optimal. The performance of OpenJDK JVM is low compared to the Oracle JVM, because it uses soft-floats instead of hard-floats. We'll need to install the Oracle JDK:
sudo apt-get install oracle-java8-jdk
Some additional packages will be installed, but that's fine. Now we're ready to install the UniFi controller itself.
Installing the UniFi controllerTo be able to use packages from the UBNT repository for Debian, we need to tell our package manager where the repository is located. We'll need to create the file /etc/apt/sources.list.d/unifi.list with the following content: deb http://www.ubnt.com/downloads/unifi/debian stable ubiquiti. You can do so manually, or let me do it for you by running the following command.
echo 'deb http://www.ubnt.com/downloads/unifi/debian stable ubiquiti' | sudo tee /etc/apt/sources.list.d/unifi.list
Next, we'll need to pass it the UBNT public key (you can verify it here) for this repo:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv C0A52C50
Then we'll tell apt to update its index and install the unifi package.
sudo apt-get update sudo apt-get install unifi
Let's have a look at the output of the last command.
pi@raspberrypi:~ $ sudo apt-get install unifi Reading package lists... Done Building dependency tree Reading state information... Done The following extra packages will be installed: ca-certificates-java icedtea-7-jre-jamvm java-common jsvc libasyncns0 libboost-atomic1.55.0 libboost-filesystem1.55.0 libboost-program-options1.55.0 libboost-system1.55.0 libboost-thread1.55.0 libcommons-daemon-java libflac8 libice6 liblcms2-2 libnspr4 libnss3 libogg0 libpcap0.8 libpcrecpp0 libpulse0 libsctp1 libsm6 libsnappy1 libsndfile1 libv8-3.14.5 libvorbis0a libvorbisenc2 libx11-xcb1 lksctp-tools mongodb-clients mongodb-server openjdk-7-jre-headless tzdata-java Suggested packages: default-jre equivs liblcms2-utils pulseaudio sun-java6-fonts fonts-dejavu-extra fonts-ipafont-gothic fonts-ipafont-mincho ttf-wqy-microhei ttf-wqy-zenhei fonts-indic The following NEW packages will be installed: ca-certificates-java icedtea-7-jre-jamvm java-common jsvc libasyncns0 libboost-atomic1.55.0 libboost-filesystem1.55.0 libboost-program-options1.55.0 libboost-system1.55.0 libboost-thread1.55.0 libcommons-daemon-java libflac8 libice6 liblcms2-2 libnspr4 libnss3 libogg0 libpcap0.8 libpcrecpp0 libpulse0 libsctp1 libsm6 libsnappy1 libsndfile1 libv8-3.14.5 libvorbis0a libvorbisenc2 libx11-xcb1 lksctp-tools mongodb-clients mongodb-server openjdk-7-jre-headless tzdata-java unifi 0 upgraded, 34 newly installed, 0 to remove and 0 not upgraded. Need to get 189 MB of archives. After this operation, 314 MB of additional disk space will be used. Do you want to continue? [Y/n]
You can see OpenJDK 7 (openjdk-7-jre-headless) is being installed. We won't need it, but it's not easy to exclude and it won't really harm us if there's plenty of disk space around.
After the installation is complete, stop the unifi service, so we can start configuring it.
sudo systemctl stop unifi
The important part: modifying the start scriptBy default, the UniFi controller will run in the OpenJDK 7 JVM and that's not what we want. To tell it to use the Oracle 8 JVM, we'll need to modify the start script located at /usr/li/unifi/bin/unifi.init. It contains a function called set_java_home detecting all sorts of OpenJDK 6 and 7 JVMs, but it can't detect Oracle JVMs. The solution is to replace this detection mechanism:
sudo sed -i 's@^set_java_home$@#set_java_home\n\n# Use Oracle Java 8 JVM instead.\nJAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt@' /usr/lib/unifi/bin/unifi.init
It replaces the line set_java_home by JAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt to skip JVM detection entirely and use the Oracle 8 JVM right away.
Note that with UniFi version 4.8.9 (which is currently in beta) or higher, the command above doesn't work due to differences in the start script. This new start script allows to set the JAVA_HOME environment variable instead. This can be done in the systemd unit file.
Copy the default systemd unit file to /etc/systemd/system/unifi.service so we can override some settings.
sudo cp /lib/systemd/system/unifi.service /etc/systemd/system/ sudo sed -i '/^\[Service\]$/a Environment=JAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt' /etc/systemd/system/unifi.service
It adds the line Environment=JAVA_HOME=/usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt under the [Service] section in the newly created unit file.
Make systemd aware of the change in its unit files.
sudo systemctl daemon-reload
When we're at it, we might also want to adjust the memory limit available to the UniFi controller which is 1 GB by default. Using a Raspberry Pi with 512 MB of RAM, in many cases, 384 MB would do. You can configure this using:
sudo sed -i 's@-Xmx1024M@-Xmx384M@' /usr/lib/unifi/bin/unifi.init
It replaces the -Xmx1024M JVM option with -Xmx384M.
If you're using a Raspberry Pi 2 model B with 1 GB of RAM, you might want to give it 768M instead:
sudo sed -i 's@-Xmx1024M@-Xmx768M@' /usr/lib/unifi/bin/unifi.init
At this point, you should be able to run the controller already, but I advise you also to work through the next section to let the controller run as its own user. This would prevent immediate root access when the controller would be exploited. If you however decide you want the controller to run as root, you can skip this part.
Configuring the controller to run as its own userUPDATE December 31, 2015: there seems to be some issues when configuring the service to run as a different user. Please skip this section for now while I'm working on a solution.
We'll create the new user first. The -r flag means it will be a system user, which has no password and cannot log in.
sudo useradd -r unifi
Make the new user owner of the various directories the UniFi controller needs to be able to write to.
sudo chown -R unifi:unifi /var/lib/unifi /var/log/unifi /var/run/unifi /usr/lib/unifi/work
This tells which user to run the service as:
sudo sed -i '/^\[Service\]$/a User=unifi' /etc/systemd/system/unifi.service
It adds the line User=unifi under the [Service] section in the systemd unit file which was created earlier on.
Make systemd aware of the change in its unit files.
sudo systemctl daemon-reload
Running the controllerYou can now start the UniFi controller with the following command. It should also run automatically after the Raspberry Pi is rebooted.
sudo systemctl start unifi
It will take some time (about 2 minutes on my old RPi model B rev 2) for the controller to start, but eventually you should be able to connect to https://<ip.of.your.rpi>:8443.
If things are not working for you, you should view the server's log file.
sudo tail /var/log/unifi/server.log
You can (and should) also verify the controller is running as the unifi user. The first column shows the name of the user.
pi@raspberrypi:~ $ ps aux | grep unifi unifi 9851 0.0 0.2 2072 1060 ? Ss 17:30 0:00 unifi -home /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt -cp /usr/share/java/commons-daemon.jar:/usr/lib/unifi/lib/ace.jar -pidfile /var/run/unifi/unifi.pid -procname unifi -outfile SYSLOG -errfile SYSLOG -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx384M com.ubnt.ace.Launcher start unifi 9853 0.0 0.2 2072 1264 ? S 17:30 0:00 unifi -home /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt -cp /usr/share/java/commons-daemon.jar:/usr/lib/unifi/lib/ace.jar -pidfile /var/run/unifi/unifi.pid -procname unifi -outfile SYSLOG -errfile SYSLOG -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx384M com.ubnt.ace.Launcher start unifi 9854 0.3 5.2 461592 25896 ? Sl 17:30 0:46 unifi -home /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt -cp /usr/share/java/commons-daemon.jar:/usr/lib/unifi/lib/ace.jar -pidfile /var/run/unifi/unifi.pid -procname unifi -outfile SYSLOG -errfile SYSLOG -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx384M com.ubnt.ace.Launcher start unifi 9872 1.3 15.6 1151876 77180 ? Sl 17:30 3:27 /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt/jre/bin/java -Xmx1024M -Dapple.awt.UIElement=true -jar /usr/lib/unifi/lib/ace.jar start unifi 9888 0.8 18.2 220780 89892 ? Sl 17:31 2:08 bin/mongod --dbpath /usr/lib/unifi/data/db --port 27117 --logappend --logpath logs/mongod.log --nohttpinterface --bind_ip 127.0.0.1 pi 10027 0.0 0.3 4252 1844 pts/0 S+ 21:38 0:00 grep --color=auto unifi
Note that it also shows you it is using the Oracle 8 JVM and the lowered memory setting.