Apache, PHP on VPS

This post describes how to setup a VPS (virtual private server) to run Apache, PHP and MySQL on CentOS. Usually I configure only the LAMP stack on dev servers, but I had to go through some additional configuration when setting up a production web server with Apache + MySQL + PHP5 on CentOS 5.8 running on VPS. VPS servers are pretty cheap and the best way is to start with a bare bones server running CentOS 5.8 and then you can configure only the things you need. This gives you complete control on what runs on the server, but then you need to setup DNS nameserver and iptables firewall. This VPS instance uses a tiny amount of memory (total server memory is 128MB) and is running on CentOS 5.8. The intent is to setup a fully functional server that runs DNS nameserver, is secured using iptables and configured to use Apache Httpd, PHP5 and MySQL for production use. It uses CentOS services to manage all the applications and is configured to use log rotation.

Here's a quick overview of how to secure a VPS (virtual private server) running CentOS 5.8x and configure Apache and PHP5.

  • Setup nameserver
  • Setup iptables
  • Setup MySQL
  • Setup PHP5
  • Configure Apache


  1. yum -list bind-*
  2. yum install bind-chroot.x86_64 (if needed)
  3. vi /etc/resolv.conf
  4. vi /var/named/chroot/etc/named.conf
  5. vi /var/named/chroot/var/named/mydomain.com.zone
  6. chmod 640 mydomain.com.zone
  7. chown root:named mydomain.com.zone
  8. service named restart

resolv.conf (/etc/resolv.conf)

options {  
    listen-on    port 53 {; }; 
    listen-on-v6 port 53 { ::1; }; 
    version             "none"; 
    directory           "/var/named"; 
    dump-file           "/var/named/data/cache_dump.db"; 
    statistics-file     "/var/named/data/named_stats.txt"; 
    memstatistics-file  "/var/named/data/named_mem_stats.txt"; 
    allow-query         { localhost; }; 
    allow-query-cache   { localhost; }; 
logging {  
    channel my_log { 
        file "data/named.run" versions 3 size 5m; 
        severity debug; 
        print-time yes; 
        print-severity yes; 
        print-category yes; 
    category default { 
view "localhost_resolver" {  
    match-clients       { localhost; }; 
    match-destinations  { localhost; }; 
    recursion           yes; 
    include             "/etc/named.rfc1912.zones"; 

named_local.conf (/var/named/chroot/etc/named.conf)

options {  
  listen-on     port 53 { any; };
  listen-on-v6  port 53 { any; };
  version             "none";
  directory           "/var/named";
  dump-file           "/var/named/data/cache_dump.db";
  statistics-file     "/var/named/data/named_stats.txt";
  memstatistics-file  "/var/named/data/named_mem_stats.txt";
logging {  
  channel my_log {
    file "data/named.run" versions 3 size 5m;
    severity warning;
    print-time yes;
    print-severity yes;
    print-category yes;
  category default {
view "localhost_resolver" {  
  match-clients       { localhost; };
  match-destinations  { localhost; };
  recursion yes;
  include "/etc/named.rfc1912.zones";
  zone "mydomain.com" {
    type master;
    file "/var/named/mydomain.com.zone";
view "external" {  
  match-clients       { any; };
  match-destinations  { any; };
  recursion no;
  allow-query-cache   { none; };
  zone "mydomain.com" {
    type master;
    file "/var/named/mydomain.com.zone";

mydomain.com.zone (/var/named/chroot/var/named/mydomain.com.zone)

@   IN  SOA   ns1.mydomain.com. hostmaster.mydomain.com. (
2012090601    ; serial, todays date  
12H           ; refresh  
4H            ; retry  
28D           ; expire  
1D )          ; minimum  
IN    NS      ns1.mydomain.com.  
IN    NS      ns2.mydomain.com.  
IN    A       123.456.789.100  
ns1                 IN    A       123.456.789.100  
ns2                 IN    A       123.456.789.101  
localhost           IN    A  
www                 IN    CNAME   mydomain.com.  

using named.root.hints file

  1. cp /usr/share/doc/bind-*/sample/etc/named.rfc1912.zones /var/named/chroot/etc
  2. cp /usr/share/doc/bind-*/sample/etc/named.root.hints /var/named/chroot/etc
  3. wget --user=ftp --password=ftp ftp://ftp.rs.internic.net/domain/root.zone -O /var/named/chroot/var/named/named.root
  4. rndc reload


  1. yum list iptables*
  2. iptables -L
  3. system-config-securitylevel
  4. iptables-save > iptables_default.out
  5. service iptables save
# Flush all current rules from iptables
iptables -F  
# Set access for localhost
iptables -A INPUT -i lo -j ACCEPT  
iptables -A INPUT -s -d -j ACCEPT  
iptables -A INPUT -s -d -j ACCEPT  
# Accept packets belonging to established and related connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT  
# Allow SSH connections on tcp port 22
# Your IP:   123.456.0.0/16
iptables -A INPUT -p tcp -s 123.456.0.0/16   --dport 22 -j ACCEPT  
# Lock down the system
iptables -P INPUT DROP  
iptables -P FORWARD DROP  
iptables -P OUTPUT ACCEPT  
# Open DNS and HTTP ports
iptables -A INPUT -p tcp -m tcp --dport 53   -j ACCEPT  
iptables -A INPUT -p udp -m udp --dport 53   -j ACCEPT  
iptables -A INPUT -p tcp -m tcp --dport 80   -j ACCEPT  
iptables -A INPUT -p tcp -m tcp --dport 443  -j ACCEPT  
# Save settings
service iptables save  
# List rules
iptables -L -v  


  1. Install Remi repository for CentOS 5.8
  2. Check available MySQL versions
    • yum --enablerepo=remi,remi-test list mysql mysql-server
  3. Install MySQL
    • yum --enablerepo=remi,remi-test install mysql.x8664 mysql-server.x8664
  4. Start MySQL and enable auto start on boot
    • /etc/init.d/mysqld start
    • chkconfig --levels 235 mysqld on
  5. MySQL secure installation
    • usr/bin/mysqlsecureinstallation
    • mysqladmin -u root password [yourpasswordhere]


  1. yum list php5*
  2. yum install php53.x8664 php53-mcrypt.x8664 php53-mysql.x86_64
  3. yum list pcre ;make sure you're using 8.x and not 6.x
    • ERROR: PHP Warning: pregmatchall(): Compilation failed: unrecognized character after (?< at offset 3
    • REASON: Upgrade pcre package to 8.10x from 6.6x
  4. rpm --import http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka
  5. nano -w /etc/yum.repos.d/utterramblings.repo
  6. Type this in editor::
    [utterramblings] name=Jason's Utter Ramblings Repo baseurl=http://www.jasonlitka.com/media/EL$releasever/$basearch/ enabled=1 gpgcheck=1 gpgkey=http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka
  7. yum --enablerepo=utterramblings list pcre
  8. yum --enablerepo=utterramblings install pcre


  1. configuration file - /etc/httpd/conf/httpd.conf
  2. log files - /etc/httpd/logs
  3. html dir - /var/www/html
  4. apache log rotation - /etc/logrotate.d/apache
    • edit config: vi /etc/logrotate.d/apache
    • test run: /etc/cron.daily/logrotate


ServerRoot "/etc/httpd"  
PidFile run/httpd.pid  
Timeout 120  
KeepAlive Off  
MaxKeepAliveRequests 100  
KeepAliveTimeout 15

<IfModule prefork.c>  
  StartServers            8
  MinSpareServers         5
  MaxSpareServers        20
  ServerLimit           256
  MaxClients            256
  MaxRequestsPerChild  4000

<IfModule worker.c>  
  StartServers            2
  MaxClients            150
  MinSpareThreads        25
  MaxSpareThreads        75
  ThreadsPerChild        25
  MaxRequestsPerChild     0

Listen 80

LoadModule authz_host_module modules/mod_authz_host.so  
LoadModule include_module modules/mod_include.so  
LoadModule log_config_module modules/mod_log_config.so  
LoadModule logio_module modules/mod_logio.so  
LoadModule env_module modules/mod_env.so  
LoadModule ext_filter_module modules/mod_ext_filter.so  
LoadModule expires_module modules/mod_expires.so  
LoadModule deflate_module modules/mod_deflate.so  
LoadModule headers_module modules/mod_headers.so  
LoadModule setenvif_module modules/mod_setenvif.so  
LoadModule mime_module modules/mod_mime.so  
LoadModule dir_module modules/mod_dir.so  
LoadModule alias_module modules/mod_alias.so  
LoadModule rewrite_module modules/mod_rewrite.so

Include conf.d/*.conf

User apache  
Group apache

ServerAdmin root@localhost  
ServerName www.mydomain.com:80  
UseCanonicalName Off  
DocumentRoot "/var/www/html"

<Directory />  
  Options FollowSymLinks
  AllowOverride None

<Directory "/var/www/html">  
  Options FollowSymLinks
  AllowOverride None
  Order allow,deny
  Allow from all

  #BSH: redirect to www, forward everything to index.php
  <IfModule mod_rewrite.c>
    RewriteEngine on

    RewriteCond %{HTTPS} off
    RewriteCond %{HTTP_HOST} !^www\.mydomain\.com$ [NC]
    RewriteRule .? http://www.mydomain.com%{REQUEST_URI} [R=301,L]

    RewriteCond %{HTTPS} on
    RewriteCond %{HTTP_HOST} !^www\.mydomain\.com$ [NC]
    RewriteRule .? https://www.mydomain.com%{REQUEST_URI} [R=301,L]

    RewriteCond %{HTTPS} off
    RewriteCond %{REQUEST_URI} api.* [OR]
    RewriteCond %{REQUEST_URI} app.* [OR]
    RewriteCond %{REQUEST_URI} login.*
    RewriteRule .? https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

    RewriteCond %{REQUEST_URI} api.* [OR]
    RewriteCond %{REQUEST_URI} app.* [OR]
    RewriteCond %{REQUEST_URI} login.* [OR]
    RewriteCond %{REQUEST_URI} rdr.*
    RewriteRule ^(.*)$ /index.php [L,QSA]


<IfModule mod_userdir.c>  
    UserDir disable

DirectoryIndex index.html index.php  
AccessFileName .htaccess  
<Files ~ "^\.ht">  
  Order allow,deny
  Deny from all

TypesConfig /etc/mime.types  
DefaultType text/plain

# BSH: remove Last-Modified
FileETag MTime Size  
Header unset Last-Modified  
Header unset Server  
Header unset X-Powered-By

# BSH: set ETag for HTML files
<FilesMatch "\.(html|htm|xml|txt|xsl)$">  
    Header unset Cache-Control

# BSH: Cache static content for 12 MONTHS
<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|swf|mp3|mp4|css|js|txt|xml)$">  
  FileETag None
  Header unset ETag
  Header unset Last-Modified
  Header set Cache-Control "public"
  Header set Cache-Control "max-age=31104000, public"

HostnameLookups Off  
ServerTokens Prod  
ServerSignature Off

ErrorLog logs/error_log  
LogLevel warn  
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined  
LogFormat "%h %l %u %t \"%r\" %>s %b" common  
LogFormat "%{Referer}i -> %U" referer  
LogFormat "%{User-agent}i" agent  
CustomLog logs/access_log common

Alias /icons/ "/var/www/icons/"  
<Directory "/var/www/icons">  
  Options None
  AllowOverride None
  Order allow,deny
  Allow from all

ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"  
<Directory "/var/www/cgi-bin">  
  AllowOverride None
  Options None
  Order allow,deny
  Allow from all

AddDefaultCharset UTF-8  
AddType application/x-compress .Z  
AddType application/x-gzip .gz .tgz

# BSH: Compress text html javascript css xml
AddOutputFilterByType DEFLATE text/plain  
AddOutputFilterByType DEFLATE text/html  
AddOutputFilterByType DEFLATE text/xml  
AddOutputFilterByType DEFLATE text/css  
AddOutputFilterByType DEFLATE text/javascript  
AddOutputFilterByType DEFLATE application/xml  
AddOutputFilterByType DEFLATE application/xhtml+xml  
AddOutputFilterByType DEFLATE application/javascript  
AddOutputFilterByType DEFLATE application/x-javascript  


<IfModule prefork.c>  
    LoadModule php5_module modules/libphp5.so
<IfModule worker.c>  
    LoadModule php5_module modules/libphp5-zts.so

# BSH: php error and cookie settings
<IfModule mod_php5.c>  
    php_value display_errors          "0"
    php_value session.name            "SMBLSID"
    php_value session.use_cookies     "1"
    php_value session.cookie_httponly "1"
    php_value session.cookie_lifetime "0"
    php_value session.gc_maxlifetime  "1800"
    php_value session.gc_divisor      "100"
    php_value session.gc_probability  "100"

AddHandler php5-script .php  
AddType text/html .php  

apache log rotation

/var/log/httpd/*log {
    rotate 10
    size 25M
    maxage 60
           /sbin/service httpd reload > /dev/null 2>/dev/null || true

Baldeep Hira

bay area programmer working on mobile/tablet/web apps and enterprise cloud apps; ui/ux, html5 and everything else for a prettier web and world

  • San Francisco Bay Area
comments powered by Disqus