Setting Up a Phorge Instance
Introduction to Phorge
phorge is the successor project to phabricator after that project ceased operation in June, 2021. phorge is essentially a team-based code review tool capable of supporting large organizations that use it with source code management tools like git, SVN, or Mercurial. It enables pre-commit review, tracking changes (down to individual characters) and enables easy comparison of earlier and later revisions. Like phabricator, phorge uses differentials, queues, reviews, and kanban boards to manage software development workflows. The project is managed by a community driven effort using phorge itself to guide future development.

Figure 1: Phorge, the Successor to Phabricator
I ran across this product as a documentation contributor to FreeBSD where it is used to review not only documentation, but source code modifications to the base system and ports. In this use case it is also paired with arcanist a command-line tool for interacting with a phorge instance for formatting and staging reviews, along with some custom scripting.
This techbits article will show you how to set up and run a phorge instance on FreeBSD.
Directory Structure
The FreebSD package for phorge has already laid out the directory structure for using phorge.
The directory layout for phorge is:
/usr/local/lib/php/phorge
/conf
/local
local.json.sample
local.json
/resources
/webroot
You will need:
- A newly installed FreeBSD instance. This example used 15-CURRENT.
- A computer (laptop, desktop, virtual machine), preferably an AMD or Intel system with at least 8 GB of memory. This will be the server machine, noted as such throughout this article.
- A large, fast disk (SSD preferred) with at least 256GB is recommended
- An Internet connection (gigabit speed recommended)
- A second machine, the client, that will connect to the server
- A graphical environment on the client for typical web browsing
You will also need an Internet domain name from a registrar. Here, the domain name ptest.example.org is used.
ptest.example.org. 3600 IN A 123.123.123.123
If you want to use email for user setup and maintenance you will also need an MX record:
ptest.example.org. 3600 IN MX 10 ptest.example.org.
You will probably also need SPF, DMARC, and DKIM set up. Setting up a mail server is beyond the scope of this article, but there are many good references on the internet.
Step by step instructions follow.
Host Setup
FreeBSD 15-CURRENT has the required packages listed in this article. Other versions may work as well.
To download the FreeBSD installation .iso visit: https://www.freebsd.org/releases/ Help on the installtion of FreeBSD is provided in the FreeBSD Handbook.
OS Installation and Setup
If you already have FreeBSD installed on your host machine, skip to the next section. Install FreeBSD on the host machine. Note that the default user (here ‘jpb’) should be a member of the wheel group. Add a standard user named phorge. User phorge does not need OS administrative privileges.
Add SSH keys:
Set up ssh keys with ssh-keygen for jpb and phorge (the default is ed25519)
$ ssh-keygen
Add any remote keys needed to both jpb and phorge .ssh/authorized_keys files.
Add the following packages.
Installation, even over a fast Internet connection, will take some time. I suggest running script(1) before installing these packages. When completed, review the ’typescript’ file for any errors, omissions, or notifications.
Create a file named package_list.txt with these contents:
ImageMagick7
bash
cmdwatch
cyrus-sasl
git
groff
jq
lynx
mutt
mysql84-server
nginx
opendkim
phorgeit-arcanist-lib-php83
phorgeit-arcanist-php83
phorgeitphorge-php83
php-fpm_exporter
pkg
postfix-sasl
postgrey
py311-spf-engine
py311-certbot
py311-certbot-nginx
py311-pygments
rsync
sudo
tmux
zip
Install the packages in the file:
# script
Script started, output file is typescript
#
# for i in `cat pkg_list.txt`
do
echo
echo "----- ${i} ---------------------------------------------"
pkg install -y ${i}
echo
echo
done
Note: the phorge packages will put all phorge and php code in /usr/local/lib/php/phorge
and subdirectories.
You do not need to pull any code from the we.phorge.it website.
rc.conf, MySQL, and PHP Setup
Set up /etc/rc.conf, editing the file directly, or using sysrc(1):
hostname="ptest.example.org"
nginx_enable="YES"
mysql_enable="YES"
#php_fpm_enable="YES"
#phd_enable="YES"
#sendmail_enable="NONE"
# git_daemon_enable="YES"
but comment some out as shown.
Nginx should work immediately.
# service nginx start
(Use lynx to test. Should bring up the nginx welcome screen.)
$ lynx localhost
MySQL
The first use of mysql requires no password:
# service mysql-server start
# mysql -u root
(Set the mysql root user password immediately with:)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'secretpassword';
Exit and test logging in with new password.
Next, run
# mysql_secure_installation
to set up mysql security.
Disallow root logins at a minimum. The other questions are user choice.
Copy /usr/local/etc/mysql/my.cnf.sample
to /usr/local/etc/mysql/my.cnf
Edit the file and in section [mysqld]
add the line:
# cp /usr/local/etc/mysql/my.cnf.sample /usr/local/etc/mysql/my.cnf
[mysqld]
sql_mode = STRICT_ALL_TABLES
Exit restart mysqld.
# service mysql-server restart
There should be no errors.
For PHP, copy /usr/local/etc/php.ini-production to php.ini
Edit /usr/local/etc/php.ini as follows:
# cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini
Edit the file and make these changes:
set error_log = /var/log/php_errors.log
set post_max_size = 32M
set upload_max_filesize = 32M
set date.timezone = America/New_York (or as appropriate)
set opcache.validate_timestamps=1 (see note on phorge notification)
The php_errors.log will need to be created before first use:
# touch /var/log/php_errors.log
Next, edit /usr/local/etc/php-fpm.d/www.conf
to use a unix socket for communication:
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '0.0.0.0:port' - to listen on a TCP socket to all IPv4 addresses on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; (IPv6 and IPv4-mapped) on a specific port;
; Note: IPv4-mapped addresses are disabled by-default in
; FreeBSD for security reasons;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
; jpb changed listen parameter to switch to unix socket. Had trouble using both ipv4 and ipv6 at the same time.
;listen = 127.0.0.1:9000
listen = /var/run/php-fpm.sock
One last PHP modification is in the support/startup/preamble-utils.php
file:
# export T=/usr/local/lib/php/phorge/
Edit $T/support/startup/preamble-utils.php
At top of the file:
<?php
/**
* jpb add for clarity on HTTPS. This fixes the yellow pop-up in the
* lower left that complains about HTTP vs. HTTPS
* Must restart all after this change.
*/
$_SERVER['HTTPS'] = true;
Phorge local.json
modifications
The local.json
file can be edited directly or edited by using the phorge specific config
command:
Either edit the file directly or use $T/bin/config which will work, but will complain about connection errors to the database.
The easiest way to do this is to set a variable to access the phorge base directory, and run the config command from there:
# export T=/usr/local/lib/php/phorge/
# cd $T/conf/local
# cp local.json.sample local.json # this is the phorge local config file.
Set up "standard credentials":
# $T/bin/config set mysql.host localhost
# $T/bin/config set mysql.user phorge
# $T/bin/config set mysql.pass mysecretphorgepassword
Review local.json for all fields. See file below.
Nginx and Phorge Certificate Setup With Certbot
Start nginx with default config to set up let’s encrypt certbot.
# service nginx start
Test with lynx localhost
- this should bring up the nginx welcome screen.
Setting up Let’s Encrypt certbot
# certbot
and follow prompts
domains: ptest.example.org example.org
The certbot may indicate it successfully retreived the certificate but could not install it. The certificate and key should be in:
/usr/local/etc/letsencrypt/live/ptest.example.org/fullchain.pem
/usr/local/etc/letsencrypt/live/ptest.example.org/privkey.pem
Make the necessary changes to nginx.conf. See nginx.conf file below.
You must complete this step to receive certificate and key from Let’s Encrypt.
To check and renew weekly, edit /etc/periodic.conf
weekly_certbot_enable="YES"
You will also need a dhparam file for the certificate. I’ve placed this file in the Let’s Encrypt “live” directory:
# cd /usr/local/etc/letsencrypt/live/ptest.example.org
# openssl dhparam -out dhparam.pem -outform PEM 2048
This may take several minutes.
Now, finalize the nginx.conf
file with these steps:
Reset configuration for nginx to the file below.
# service nginx stop
Copy nginx configuration from file below to /usr/local/etc/nginx.
Now, ensure edits to server_name, and root directory are correct:
server_name ptest.example.org;
root /usr/local/lib/php/phorge/webroot;
ssl_certificate /usr/local/etc/letsencrypt/live/ptest.example.org/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/ptest.example.org/privkey.pem;
fastcgi_pass unix:/var/run/php-fpm.sock;
Once all edits are complete, test the configuration file with
# nginx -t
There should be no errors. Nginx will start, but any browsing will cause errors because we are not done yet.
MySQL and Reinitializing MySQL
Mysql server should be running. Check with:
# service mysql-server status
#If it is not started, and if # service mysql-server start
does not start mysql, you may have to reinitialize the database.
Here are the steps to reinitialize MySQL:
# cd /var/db/mysql
# rm -rf *
# ls -al
total 17
drwxr-x--- 2 mysql mysql 2 May 10 14:39 .
drwxr-xr-x 20 root wheel 23 May 10 13:18 ..
# cd ..
# /usr/local/libexec/mysqld --initialize --user=mysql --datadir=/var/db/mysql
2025-05-10T18:45:44.533006Z 0 [System] [MY-015017] [Server] MySQL Server Initialization - start.
2025-05-10T18:45:44.535872Z 0 [Warning] [MY-010915] [Server] 'NO_ZERO_DATE', 'NO_ZERO_IN_DATE' and 'ERROR_FOR_DIVISION_BY_ZERO' sql modes should be used with strict mode. They will be merged with strict mode in a future release.
2025-05-10T18:45:44.536021Z 0 [System] [MY-013169] [Server] /usr/local/libexec/mysqld (mysqld 8.4.3) initializing of server in progress as process 46752
mysqld: Error on delete of '/var/db/mysql/auto.cnf' (OS errno 2 - No such file or directory)
2025-05-10T18:45:44.788423Z 0 [Warning] [MY-010107] [Server] World-writable config file '/var/db/mysql/auto.cnf' has been removed.
2025-05-10T18:45:44.792948Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-05-10T18:45:46.641922Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2025-05-10T18:45:49.300164Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: ++mysqlSecretPassword123++
2025-05-10T18:45:52.231932Z 0 [System] [MY-015018] [Server] MySQL Server Initialization - end.
#
Note the new root password above. You will need it to log in again.
MYSQL – Create the Phorge Databases
It is now time bring up the phorge databases and tables.
First, create the needed credentials:
# mysql -u root -p (use the password from the initialization step above if needed)
root@localhost [(none)]> create user 'phorge'@'localhost' identified by 'MyPhorgeSecretPw';
root@localhost [(none)]> create database phorge character set utf8mb4 collate utf8mb4_general_ci;
root@localhost [(none)]> grant all privileges on `phabricator\_%`.* to 'phorge'@'localhost'; NOTE THE USE OF BACKTICKS HERE.
root@localhost [(none)]> grant all privileges on phorge.* to 'phorge'@'localhost';
root@localhost [(none)]> flush privileges;
root@localhost [(none)]> quit;
Then try bring up the phorge. The following command loads everything into MySQL:
$T/bin/storage upgrade --user phorge --password MyPhorgeSecretPw –-force
You should see:
Loading quickstart template onto "localhost:3306"...
Applying patch "phabricator:db.paste" to host "localhost:3306"...
Applying patch "phabricator:20190523.myisam.01.documentfield.sql" to host "localhost:3306"...
Applying patch "phabricator:20190718.paste.01.edge.sql" to host "localhost:3306"...
Applying patch "phabricator:20190718.paste.02.edgedata.sql" to host "localhost:3306"...
Applying patch "phabricator:20190718.paste.03.paste.sql" to host "localhost:3306"...
... Many more statements
Completed applying all schema adjustments.
ANALYZE Analyzing tables...
Done.
ANALYZED Analyzed 525 table(s).
root@ptest:~ #
Final Alignments and Startup
A number of parameters have to align correctly. Follow these notes to align parameters where needed:
* nginx root ‘/usr/local/lib/php/phorge/webroot’
The package puts the code here, there is no need to clone from phorge website.
* MySQL user ‘phorge’@’localhost’
* local.json mysql.user phorge (Note: not phorge@ptest.example.org)
* MySQL user phorge permissions (Note: see above)
Stop any running nginx and mysql services:
# service mysql-server stop
# service nginx stop
Ensure all phorge elements in /etc/rc.conf are enabled:
nginx_enable="YES"
mysql_enable="YES"
php_fpm_enable="YES"
phd_enable="YES"
git_daemon_enable="YES"
pygments_enabled="YES"
sshd Alignments
Copy the sshd_config.phorge.5555
file below into /etc/ssh
and edit for for local interface IP addr.
Ensure that /usr/libexec/phorge-ssh-hook.sh
exists See file below
And ensure owner of root:wheel. Script should be mode 755
LOCAL.JSON
Update local.json with the copy below.
Also change “phabricator.base-uri” to local system URI:
"phabricator.base-uri": "https://ptest.example.org",
Make sure all directories exist and have the correct permissions.
# mkdir -p /var/phorge/repos
# mkdir -p /var/phorge/files
# chown -R www:www /var/phorge
Set an alias for root in /etc/aliases
root: jpb@localhost
and, if your dmarc entry set up a report email,
dmarc-report: jpb@localhost
and run newaliases as root.
Initial Phorge Startup
Start up each service by hand one at a time and fix any errors:
# /usr/sbin/sshd -f /etc/ssh/sshd_config.phorge.5555
# service nginx start
# service mysql-server start
(Check with service mysql-server status.)
# service php_fpm start
(Should say conf test is successful.)
# service phd start
(Should note launching daemons.)
# chown www:www /var/run/php-fpm.sock
# ps -ax | grep ssh
(Should be two sshd daemons running.)
PHORGE SHOULD BE UP!
TIME SENSITIVE - DO IMMEDIATELY
Web to https://ptest.example.org
Should see the initial phorge admin screen.
Set the admin account and password!
Then use the ph.sh script to stop everything.

Figure 2: Completed Phorge Instance
Configuration Files for nginx, local.json,
Nginx.conf file for phorge
---------------------------------------------------------
# jpb changed user to www
user www;
# jpb changed to 4
worker_processes 4;
# This default error log path is compiled-in to make sure configuration parsing
# errors are logged somewhere, especially during unattended boot when stderr
# isn't normally logged anywhere. This path will be touched on every nginx
# start regardless of error log location configured here. See
# https://trac.nginx.org/nginx/ticket/147 for more info.
#
#error_log /var/log/nginx/error.log;
#
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# jpb uncommented log_format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
# jpb added these comments:
# Configure both http and https connections
# https://nginx.org/en/docs/http/configuring_https_servers.html
#
# Server block 1 - Redirect HTTP -> HTTPS for server name
server {
listen 80;
server_name ptest.example.org;
# Redirect everything to HTTPS
return 301 https://$host$request_uri;
}
# Server block 2 - Serve phorge dynamic content for server name
server {
listen 443 ssl;
server_name ptest.example.org;
root /usr/local/lib/php/phorge/webroot;
# Configure ssl certificate. This cert is used by postfix as well.
ssl_certificate /usr/local/etc/letsencrypt/live/ptest.example.org/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/ptest.example.org/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_session_tickets off;
ssl_prefer_server_ciphers on;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_dhparam /usr/local/etc/letsencrypt/live/ptest.example.org/dhparam.pem;
location / {
index index.php;
rewrite ^/(.*)$ /index.php?__path__=/$1 last;
}
location /index.php {
# out jpb fastcgi_pass localhost:9000; could not get ipv4 and ipv4 both working
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
#required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
#variables to make the $_SERVER populate in PHP
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
}
}
}
local.json file:
local.json file for phorge
---------------------------------------------------------
{
"cluster.mailers": [
{
"key": "myphorge-sendmail",
"type": "sendmail"
}
],
"environment.append-paths": [
"/bin",
"/usr/bin",
"/usr/local/bin",
"/usr/local/libexec/git-core"
],
"diffusion.allow-http-auth": true,
"pygments.enabled": true,
"mysql.host": "localhost",
"mysql.pass": "mysecretphorgepassword",
"mysql.user": "phorge",
"phd.user": "www",
"phd.log-directory": "/var/log",
"mysql.port": "3306",
"repository.default-local-path": "/var/phorge/repos",
"storage.local-disk.path": "/var/phorge/files",
"phabricator.base-uri": "https://ptest.example.org",
"phabricator.show-prototypes": true,
"diffusion.ssh-user": "git",
"diffusion.ssh-port": 5555
}
Control script ph.sh
for starting, stopping phorge:
---------------------------------------------------------
#!/bin/sh
usage(){
echo "ph.sh start | stop | status"
echo " one of start, stop, or status must be supplied."
exit 1
}
# jpb phorge-specific ssh for accessing repos
# See https://we.phorge.it/book/phorge/article/diffusion_hosting/
# Configuring SSH
#
# This is the pid file for the extra ssh daemon
# The pid file is specified in the config file.
PHORGE_SSHD_PID=/var/run/sshd_5555.pid
PHORGE_SSHD_CONFIG=/etc/ssh/sshd_config.5555
if [ $# -ne 1 ]
then
usage;
fi
CMD=$1
case $CMD in
stop)
set -x
service phd ${CMD}
service php_fpm ${CMD}
service mysql-server ${CMD}
service nginx ${CMD}
service git_daemon ${CMD}
kill -TERM `cat ${PHORGE_SSHD_PID}`
set +x
exit 0
;;
start)
set -x
/usr/sbin/sshd -f ${PHORGE_SSHD_CONFIG}
service git_daemon ${CMD}
service nginx ${CMD}
service mysql-server ${CMD}
service php_fpm ${CMD}
service phd ${CMD}
# must set ownership on socket at /var/run/php-fpm.sock
chown www:www /var/run/php-fpm.sock
ps -ax | grep ssh
set +x
exit 0
;;
status)
set -x
service nginx ${CMD}
service mysql-server ${CMD}
service php_fpm ${CMD}
service phd ${CMD}
service git_daemon ${CMD}
ps -ax | grep ssh
set +x
exit 0
;;
*)
usage;
esac
exit 0
Control script phorge-ssh-hook.sh
for enabling git to use ssh
Note: place this shell in /usr/libexec/phorge-ssh-hook.sh, mode 0755, and make sure it is executable
#!/bin/sh
# NOTE: Replace this with the username that you expect users to connect with.
# jpb changed VCSUSER to git
VCSUSER="git"
# NOTE: Replace this with the path to your Phorge directory.
ROOT="/usr/local/lib/php/phorge"
if [ "$1" != "$VCSUSER" ];
then
exit 1
fi
exec "$ROOT/bin/ssh-auth" $@
#end of script file
Configuration script /etc/ssh/sshd_config.phorge.5555
Note: place this configuration file in /etc/ssh:
# NOTE: You must have OpenSSHD 6.2 or newer; support for AuthorizedKeysCommand
# was added in this version.
# jpb - sshd for phorge. See https://we.phorge.it/book/phorge/article/diffusion_hosting/
# NOTE: Edit these to the correct values for your setup.
AuthorizedKeysCommand /usr/libexec/phorge-ssh-hook.sh
AuthorizedKeysCommandUser git
AllowUsers git
#
# NOTE! NOTE! NOTE!
#
# The only authorized user of this configuration is the "git" user - see above.
# Only use user "git" for testing.
# You may need to tweak these options, but mostly they just turn off everything
# dangerous.
Port 5555
ListenAddress 69.87.220.138
ListenAddress 2607:f170:44:13::210
PubkeyAuthentication yes
#Protocol 2
PermitRootLogin no
AllowAgentForwarding no
AllowTcpForwarding no
PrintMotd no
# jpb Added Pid file
# Must agree with root's ph.sh script.
PidFile /var/run/sshd_5555.pid
PasswordAuthentication no
UsePAM no
ChallengeResponseAuthentication no
ClientAliveInterval 60
#AuthorizedKeysFile none
AuthorizedKeysFile .ssh/authorized_keys