Please keep in mind that this post was written more than 2 years ago and might be outdated.

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

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.