Step by step guide how to configure server for PHP / Symfony project
I decided to publish notes that I have been gathering lately about configuring servers. This is a complete guide how to configure server for production usage with Symfony or any PHP project. Instruction might seem to you very long and I totally agree with you. It took me few months to finish.
These steps are about preparing your server for PHP / Symfony application and do not cover code deployment itself.
If you found this useful leave a comment or share link to your friends, it's always good to know that someone (besides Googlebot) actually read this :)
All steps should be done in order of reading. Configuration should not take you more than two hours. It was tested by me on ~10 machines, however if you find something not working don't hesitate to let me know. I try to help you and correct/expand guide to cover your problem.
Requirements:
- OS: Debian 6.0 or 7.0
You will get server with:
- Nginx (latest stable), using sockets to communicate with PHP rather than TCP/IP
- PHP 5.4 or 5.5 (latest stable)
- MySQL 5.5 (production settings)
- NodeJS (latest stable), including: Less, Uglify-JS
- Twig C extension
- GIT
- Optional: Zend OPCache, APCu
Security:
- Disabled direct root access
- Installed sudo
- Installed fail2ban (deny access after 3 incorrect login attempts)
- Non privileged user for deployments
- Authorization using ssh keys
Other
- Correct timezone
- Fixed common locale warnings
- Color prompt
- Removed Apache2
Step by step guide how to configure server for PHP / Symfony project
Mass replace
You can copy guide to your favorite editor and use mass replace function, this way later you can just copy paste all commands.
Use mass replace function to change following unique strings
- SERVERIPADDRESS to your actual IPv4 ip address
- NONPRIVILAGEUNIXUSERNAME to unix username you want to use, e.g. konrad
- APPDOMAINNAME to domain name without http:// and trailing slash e.g. konradpodgorski.com
Start
First log in
ssh root@SERVERIPADDRESS
On server create new user
useradd NONPRIVILAGEUNIXUSERNAME -m -s /bin/bash
Update apt-get
apt-get update
Install nano
apt-get install nano
Install sudo
apt-get install sudo
nano /etc/sudoers
Before
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults env_reset
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Host alias specification
# User alias specification
# Cmnd alias specification
# User privilege specification
root ALL=(ALL:ALL) ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
# See sudoers(5) for more information on "#include" directives:
After (added: NONPRIVILAGEUNIXUSERNAME ALL=(ALL) NOPASSWD: ALL)
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults env_reset
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Host alias specification
# User alias specification
# Cmnd alias specification
# User privilege specification
root ALL=(ALL:ALL) ALL
NONPRIVILAGEUNIXUSERNAME ALL=(ALL) NOPASSWD: ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
# See sudoers(5) for more information on "#include" directives:
Switch user you just created
su NONPRIVILAGEUNIXUSERNAME
Add your public key to an authorized keys
cd ~
mkdir .ssh
nano .ssh/authorized_keys
Paste your public key and save, set proper chmod
chmod 700 .ssh -R
logout and check if you can log in as NONPRIVILAGEUNIXUSERNAME directly without password
ssh NONPRIVILAGEUNIXUSERNAME@SERVERIPADDRESS
Disable log in with password (you will use public key anyway)
sudo passwd -l NONPRIVILAGEUNIXUSERNAME
This command locks the password
Color prompt (optional)
nano ~/.bashrc
Uncomment following line (remove #)
#force_color_prompt=yes
fail2ban - solution for brute force attacks
sudo apt-get install fail2ban
Disable direct log in on root account
sudo nano /etc/ssh/sshd_config
Find and change from yes to no (Ctrl + W in nano)
PermitRootLogin no
We will need these to compile Node JS from sources later
To do that install
sudo apt-get update
sudo apt-get install -y gcc g++ make
Server timezone
sudo dpkg-reconfigure tzdata
Locale stuff
Get rid of annoying errors when not using EN_US locale
http://hexample.com/2012/02/05/fixing-locale-problem-debian/
Change hostname to something better e.g. symfony-app-server (you cannot use dots)
This step is optional
sudo nano /etc/hostname
Put same host in /etc/hosts
sudo nano /etc/hosts
restart server
sudo reboot
Dotdeb
DotDeb is a reliable and up to date repository for most of packages required in web server
Instruction was copied from official DotDeb site http://www.dotdeb.org/instructions/
First add the dotdeb repo to your sources.list file:
sudo nano /etc/apt/sources.list
add this to the bottom of the file:
Debian 7.0 Wheezy
deb http://packages.dotdeb.org wheezy all
deb-src http://packages.dotdeb.org wheezy all
or Debian 6.0 Squeeze
deb http://packages.dotdeb.org squeeze all
deb-src http://packages.dotdeb.org squeeze all
If you want to install :
- PHP 5.5 on Debian 7.0 “Wheezy”, add these two lines too :
deb http://packages.dotdeb.org wheezy-php55 all
deb-src http://packages.dotdeb.org wheezy-php55 all
- PHP 5.4 on Debian 6.0 “Squeeze”, add these two lines too :
deb http://packages.dotdeb.org squeeze-php54 all
deb-src http://packages.dotdeb.org squeeze-php54 all
Next, add the GnuPG key to your distribution:
wget http://www.dotdeb.org/dotdeb.gpg
cat dotdeb.gpg | sudo apt-key add -
Update APT:
sudo apt-get update
NGINX
You probably won't need most of it features (like proxy, mail) so nginx-light should be enough. Latest stable release from dotdeb.org
sudo apt-get install nginx-light
NGINX - General Configuration
Note: set worker_processes 2;
to the number of cpu cores your server has
#/etc/nginx/nginx.conf
user www-data;
worker_processes 2;
pid /var/run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
NGINX - configuration specific for application
create configuration file
sudo nano /etc/nginx/sites-available/APPDOMAINNAME
edit #/etc/nginx/sites-available/APPDOMAINNAME
# /etc/nginx/sites-available/APPDOMAINNAME
upstream php5-fpm {
server unix:/var/run/php5-fpm.sock;
}
# redirect from www to non-www
server {
listen 80;
server_name www.APPDOMAINNAME;
return 301 $scheme://APPDOMAINNAME$request_uri;
}
server {
listen 80;
server_name APPDOMAINNAME;
root /var/www/APPDOMAINNAME/current/web;
client_max_body_size 256M;
# strip app.php/ prefix if it is present
rewrite ^/app\.php/?(.*)$ /$1 permanent;
location / {
index app.php app_dev.php;
try_files $uri @rewriteapp;
}
location @rewriteapp {
rewrite ^(.*)$ /app.php/$1 last;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ ^/(app|app_dev)\.php(/|$) {
fastcgi_pass php5-fpm;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
}
}
Enable site
sudo ln -s /etc/nginx/sites-available/APPDOMAINNAME /etc/nginx/sites-enabled/APPDOMAINNAME
Remove default
sudo rm /etc/nginx/sites-enabled/default
PHP5-FPM
Installing required packages
sudo apt-get install -y php5 php5-fpm php-pear php5-common php5-mcrypt php5-mysql php5-cli php5-gd php5-intl php5-curl php5-dev
pool.d/www.conf
Update (June 2014): since the time I created this guide php5-fpm team changed /etc/php5/fpm/pool.d/www.conf
default settings to described below. You don't have to change anything from this section. I'm leaving it as a reference in case someone use old config.
Edit
# /etc/php5/fpm/pool.d/www.conf
Find and edit if needed user and group
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
; will be used.
user = www-data
group = www-data
Find and replace
listen = 127.0.0.1:9000
with
listen = /var/run/php5-fpm.sock
php.ini configuration
FPM (used by web server - Nginx)
# /etc/php5/fpm/php.ini
date.timezone = Europe/Warsaw
short_open_tag = Off
expose_php = off
max_execution_time = 60
memory_limit = 256M
post_max_size = 128M
upload_max_filesize = 128M
CLI (used in console)
# /etc/php5/cli/php.ini
date.timezone = Europe/Warsaw
short_open_tag = Off
Twig extension
sudo pear channel-discover pear.twig-project.org
sudo pear install twig/CTwig
sudo nano /etc/php5/mods-available/twig.ini
Paste inside
extension=twig.so
Zend OPCache
sudo pecl install zendopcache-7.0.2
sudo nano /etc/php5/mods-available/opcache.ini
# /etc/php5/mods-available/opcache.ini
zend_extension=/usr/lib/php5/20100525/opcache.so
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
cd /etc/php5/conf.d
sudo ln -s ../mods-available/opcache.ini 05-opcache.ini
APCu
git clone https://github.com/krakjoe/apcu
cd apcu
phpize
./configure
make
sudo make install
sudo nano /etc/php5/mods-available/apcu.ini
# /etc/php5/mods-available/apcu.ini
extension=apcu.so
apc.enabled=1
apc.shm_size=32M
apc.ttl=7200
apc.gc_ttl=3600
apc.enable_cli=0
cd /etc/php5/conf.d
sudo ln -s ../mods-available/apcu.ini 20-apcu.ini
Restart PHP5-FPM
For all changes to take an effect you need to restart php
sudo service php5-fpm restart
MySQL
sudo apt-get install -y mysql-server
In a window that popped up enter root password, use program like KeePassX to generate secure password
Tweak configuration
sudo nano /etc/mysql/my.cnf
Add innodb_file_per_table right after [mysqld]
[mysqld]
innodb_file_per_table
Restart mysql for changes to take effect
sudo service mysql restart
Next run and follow instructions
sudo mysql_secure_installation
Install MySQL Workbench for secure managing your database over a SSH tunnel. http://www.mysql.com/products/workbench/
DO NOT expose your mysql to outside world nor install web management tools like PhpMyAdmin, they were great few years ago. Now we have a better solutions.
NodeJS
optional - I use it for compiling less files and minimizing javascripts with Uglify-JS
It's better to concat and minify assets before deployment. If you are working with multiple instances it will speed up deployment process by few seconds for each instance.
This is a common fact that NodeJS in Debian/Ubuntu is seriously outdated. Installing from source is nice and easy.
Install make and required compilers
sudo apt-get update
sudo apt-get install -y gcc g++ make
Get source and install
Sadly there are no permalink for latest stable version so you are on your own with that. Go to http://nodejs.org/dist/latest/ and search for file matching the following pattern
node-v0.10.*.tar.gz
wget http://nodejs.org/dist/latest/node-v0.10.29.tar.gz
tar -zxvf node-v0.10.29.tar.gz
cd node-v0.10.29
./configure
make
sudo make install
Installing LESS compiler
sudo npm install -g less
Installing uglify-js
sudo npm install -g uglify-js
GIT
sudo apt-get install -y git
Remove Apache2, important!
sudo service apache2 stop
We don't need apache2 so remove it
sudo apt-get remove apache2
Otherwise it will take 80 port after next reboot and nginx will not work
User for deploying
We will use www-data user to deploy code.
Add your public key
sudo mkdir /var/www/.ssh -p
sudo nano /var/www/.ssh/authorized_keys
It's super important that owner and group of whole .ssh should be root. This way with read permission user www-data will be able to read public key but won't be able to add new one or edit existing.
With this we won't give www-data any more permissions than it already has.
Directory for application
sudo mkdir /var/www/APPDOMAINNAME -p
sudo chown www-data:www-data /var/www/APPDOMAINNAME
Deploying with Capifony
This is super important step. Github allows only for 60 requests per hour for non authenticated connections. Using composer cache will prevent getting banned
sudo mkdir /var/www/.composer
sudo chown www-data:www-data /var/www/.composer
Also a Curl is required if you are using Capifony for deployment
sudo apt-get install curl
Test everything by restarting server
sudo reboot
That's it!
Thank you for taking time to read this guide.