Building and Installing HHVM on CentOS 7
What’s HHVM?
HHVM is an open-source virtual machine designed for executing programs written in Hack and PHP. Instead of interpreting PHP directly, it uses a just-in-time (JIT) compiler to reach much higher throughput while staying compatible with existing PHP code. Facebook, which created HHVM, reported roughly a 9x gain in web request throughput and a 5x drop in memory use over the old PHP 5.2 + APC engine, and back in 2015 HHVM could run most of the popular PHP frameworks out of the box.
Note: This post is kept as a historical record from 2015. HHVM dropped PHP support in version 4.0 (2019) and now runs only Hack, and CentOS 7 itself reached end-of-life on June 30, 2024. The steps below no longer apply to a current system. To speed up PHP today, run a recent PHP 8.x with PHP-FPM and OPcache instead. The walkthrough is preserved as a snapshot of how HHVM was built and deployed at the time.
Prerequisites
Building HHVM from source is heavy. The final link step alone can eat well over a gigabyte of memory, so compile on a machine or VPS with at least 2 GB of RAM, ideally more. On a smaller instance the build either crawls or gets cut down by the OOM killer halfway through. Expect the whole compile to take anywhere from twenty minutes to over an hour depending on your CPU.
Prepare
We are not using sudo here because it makes the columns so long. Do it yourself or just be root here, for a short while.
HHVM pulls in a long list of libraries that CentOS’s base repositories don’t all carry, so start by enabling two extra ones: EPEL (Extra Packages for Enterprise Linux) and Remi, which ships the newer multimedia packages HHVM needs, including a compatible ImageMagick:
rpm -Uvh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm # ImageMagick
With the repos in place, pull in the toolchain (GCC, CMake, Git) and the -devel header packages HHVM links against. The brace expansion is just shorthand: the shell expands {a,b}-devel into a-devel b-devel so the list stays readable instead of running off the screen:
# Use bash brace extension here or the list would be really frightening
yum install cpp gcc-c++ cmake git psmisc {binutils,boost,jemalloc}-devel \
{sqlite,tbb,bzip2,openldap,readline,elfutils-libelf,gmp,lz4,pcre}-devel \
lib{xslt,event,yaml,vpx,png,zip,icu,mcrypt,memcached,cap,dwarf}-devel \
{unixODBC,expat,mariadb}-devel lib{edit,curl,xml2,xslt}-devel \
glog-devel oniguruma-devel inotify-tools-devel ocaml
# HHVM won't build without ImageMagick from remi failing after hphp_runtime_static
yum remove ImageMagick # If it's already installed
yum install ImageMagick-last\* --enablerepo=remi # Newer one
One quirk: glog-devel installs its shared library under /usr/lib64, but HHVM’s build looks for it in /usr/lib. Bridge the two with a symlink, otherwise linking fails:
ln -s /usr/lib64/libglog.so /usr/lib/libglog.so
Get the Source
Clone the repository with --recursive. HHVM keeps several of its dependencies as Git submodules, and the build dies partway through if they aren’t all checked out:
cd /tmp
git clone https://github.com/facebook/hhvm -b master hhvm --recursive
cd hhvm
Build HHVM
HHVM builds with CMake; the bundled ./configure is just a thin wrapper around it, so call cmake directly. The three -D flags point the build at the ImageMagick libraries you installed from Remi; without them CMake either can’t find ImageMagick or links the wrong version, and the build fails late, after hphp_runtime_static, which is a frustrating place to discover the problem. The make -j line runs one job per core plus one. This is by far the most time-consuming step, and on a 2 GB box those parallel jobs are also the most likely thing to exhaust your RAM, so lower the job count if make gets killed:
# ./configure # That is a cmake wrapper, ignore it.
cmake \
-DLIBMAGICKWAND_INCLUDE_DIRS="/usr/include/ImageMagick-6" \
-DLIBMAGICKCORE_LIBRARIES="/usr/lib64/libMagickCore-6.Q16.so" \
-DLIBMAGICKWAND_LIBRARIES="/usr/lib64/libMagickWand-6.Q16.so" .
make -j$(($(nproc)+1)) # make with CORE_COUNT+1 threads, that would be fast. You may run out of RAM
# Test..
./hphp/hhvm/hhvm --version
# Install it..
sudo make install
Add HHVM to Services
CentOS 7 ships with systemd, so register HHVM as a unit. Save the following to /usr/lib/systemd/system/hhvm.service:
[Unit]
Description=HHVM HipHop Virtual Machine (FCGI)
[Service]
ExecStart=/usr/local/bin/hhvm \
--config /etc/hhvm/server.ini \
--config /etc/hhvm/php.ini \
--config /etc/hhvm/config.hdf \
--user nginx \
--mode daemon \
-vServer.Type=fastcgi \
-vServer.Port=9001
[Install]
WantedBy=multi-user.target
The unit runs HHVM in daemon mode as the nginx user and has it speak FastCGI on port 9001, the port nginx will forward PHP requests to. The three --config files don’t exist yet; you’ll create them in the next section. Now enable it so it starts on boot:
systemctl enable hhvm
systemctl start hhvm
systemctl status hhvm
Configure HHVM
HHVM reads its settings from the three files the unit references. First create the directories it writes logs and runtime state into, owned by the same nginx user the service runs as:
mkdir /etc/hhvm
mkdir /var/run/hhvm
sudo chown nginx:nginx /var/run/hhvm
mkdir /var/log/hhvm
sudo chown nginx:nginx /var/log/hhvm
config.hdf holds runtime limits and logging in HHVM’s HDF format: resource caps, where the error and access logs go, and MySQL/mail defaults. Create it in /etc/hhvm:
ResourceLimit {
CoreFileSize = 0 # in bytes
MaxSocket = 10000 # must be not 0, otherwise HHVM will not start
SocketDefaultTimeout = 5 # in seconds
MaxRSS = 0
MaxRSSPollingCycle = 0 # in seconds, how often to check max memory
DropCacheCycle = 0 # in seconds, how often to drop disk cache
}
Log {
Level = Info
AlwaysLogUnhandledExceptions = true
RuntimeErrorReportingLevel = 8191
UseLogFile = true
UseSyslog = false
File = /var/log/hhvm/error.log
Access {
* {
File = /var/log/hhvm/access.log
Format = %h %l %u %t \"%r\" %>s %b
}
}
}
MySQL {
ReadOnly = false
ConnectTimeout = 1000 # in ms
ReadTimeout = 1000 # in ms
SlowQueryThreshold = 1000 # in ms, log slow queries as errors
KillOnTimeout = false
}
Mail {
SendmailPath = /usr/sbin/sendmail -t -i
ForceExtraParameters =
}
server.ini is the server-facing config: it pins the FastCGI port (matching the unit file), the PID file, and where HHVM stores its compiled bytecode cache (hhvm.hhbc). Create it in /etc/hhvm:
; php options
pid = /var/run/hhvm/pid
; hhvm specific
hhvm.server.port = 9001
;hhvm.server.file_socket = /var/run/hhvm/sock
hhvm.server.type = fastcgi
hhvm.server.default_document = index.php
hhvm.log.use_log_file = true
hhvm.log.file = /var/log/hhvm/error.log
hhvm.repo.central.path = /var/run/hhvm/hhvm.hhbc
php.ini carries the familiar PHP knobs (memory limit, max post size) alongside a couple of HHVM-specific settings. Create it in /etc/hhvm:
hhvm.mysql.socket = /tmp/mysql.sock
hhvm.server.expose_hphp = true
memory_limit = 400M
post_max_size = 50M
Point Nginx at HHVM
HHVM is now listening on FastCGI port 9001, but nothing is sending it requests yet. The last step is to tell nginx to hand off PHP files to HHVM instead of serving them as plain text. Add a location block to your server config:
location ~ \.(hh|php)$ {
fastcgi_pass 127.0.0.1:9001;
fastcgi_index index.php;
include fastcgi_params;
}
Reload nginx with systemctl reload nginx, then drop a phpinfo() page somewhere and load it. You should see HHVM identified as the engine, confirming requests are running through the JIT rather than stock PHP.
The Disqus comment system is loading ...
If the message does not appear, please check your Disqus configuration.