The Elliquiy LAMP Stack: PHP compilation and configuration

Started by Vekseid, March 28, 2009, 07:53:15 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Vekseid

The Elliquiy LAMP Stack

1: Introduction and Overview
2: General Configuration
3: General Security
4: IPTables configuration
5: Postfix configuration
6: ntp configuration
7: Apache compilation and configuration
8: MySQL compilation and configuration
9: PHP compilation and configuration
10: Conclusion and future plans




PHP

PHP configuration can be a bit of an adventure. After tuning MySQL and switching from CGI to FastCGI, it became obvious that the hog to end all hogs is PHP. This should not be terribly surprising, but there actually is quite a lot that we can do about it.

In addition to php5-cgi, I also install apc, suhosin and imagick. A few minor items:
1) Add a symlink - ln -sv /usr/bin/convert /usr/local/bin/convert because some php scripts have trouble finding it and I'm lazy.
2) Remove the symlink  /etc/php5/cgi/conf.d and make it a static, empty directory.
3) Move php.ini into the /etc/php5/cgi/conf.d directory and either add the contents of your chosen non-builtin extensions (there should not be many) to the file or copy them over.

This will be our 'master' php.ini file. It gets loaded after the file specified by the PHPRC variable in our php.fcgi init script:


#!/bin/sh
# /var/www/ellidir/fcgi/php.fcgi
umask 022
export PHPRC=/var/www/ellidir/fcgi
export PHP_FCGI_CHILDREN=0
export PHP_FCGI_MAX_REQUESTS=0
exec /usr/bin/php-cgi


The file called being /var/www/ellidir/fcgi/php.ini

This lets us tune our configuration to individual sites nicely. Even better, the user only needs to be able to read from the file - it's owned by root on the server, so a disgruntled or clumsy user or malicious hacker can't use it to wreak havoc. If we want to give them write access, it still gets overwritten by php.ini from /etc/php5/cgi/conf.d if we decide to allow that for selected users. This lets us tune Suhosin and many other variables on a per-site basis.

About Accelerators

eAccelerator: Derived from Turk MMCache. Makes use of shared memory, so that variables may be passed between multiple fcgi processes on the same account. This is good, however, it is not able to handle many features of classes or potentially other php 5.2 features - much less 5.3. Trying to run eAccelerator on a modern version of MediaWiki is an exercise in futility. The bug that causes this was supposedly fixed in the development branch some three years ago, but the project has seemingly stalled.

APC: Alternative PHP Cache would seem like the logical choice as a PECL extension, however, it does not make proper use of shared memory. This means that you need to restrict yourself to one fcgi process and let php handle threading if you want to use the user cache.

XCache: Like APC, it does not handle shared memory properly. Unlike APC, at least in the Debian Lenny configuration, it segfaults like crazy, so I don't use it anymore.

Even with the above flaws, I make use of APC as an opcode cache. This does have a rather serious memory hit but I put up with it for now.




PHP Compilation

A custom-built php binary can speed up operations over the provided packages by as much as 25%, while also reducing their memory footprint. Both of these are win-win situations.

First, my CFLAGS line:

CFLAGS = -O2 -march=core2 -Wall -fsigned-char -fno-strict-aliasing

Basically the same as the default, except no debugging aids (-g or such) and of course architecture specific tuning. I explored a lot of other options and finally reverted to this.

-Os: Reduces speed by about 10% across the board, from the prebuilt baseline. Some benchmarks on-line suggest that -Os is competitive, however I suspect this is because they were using regular cgi or the command line parser and not fastcgi.
-O3: Gives a bit of a tradeoff when compared with -O2. Complex built-in functions seem to be slightly faster, but this comes at the expense of common arithmatic, string and control functions being slower. Segfaults a fair bit, so I stick with O2.
-funroll-loops: Similar results to -O3.
-fomit-frame-pointer: This is commonly suggested, but it appears to actually reduce speed by about 2%.
-fno-strict-aliasing: It's in the Debian default rules, and does not appear to noticibly reduce performance, so I leave it.

My configuration:


COMMON_CONFIG=--build=$(DEB_BUILD_GNU_TYPE) \
                --host=$(DEB_HOST_GNU_TYPE) \
                --mandir=/usr/share/man \
                --disable-bcmath \
                --with-bz2 \
                --disable-calendar \
                --disable-ctype \
                --with-curl=/usr \
                --without-db4 \
                --disable-dbx \
                --disable-debug \
                --with-exec-dir=/usr/lib/php5/libexec \
                --enable-exif \
                --disable-filepro \
                --disable-filter \
                --enable-ftp \
                --with-gd=/usr \
                --enable-gd-native-ttf \
                --without-gdbm \
                --without-gettext \
                --without-gmp \
                --disable-hash \
                --without-iconv \
                --disable-ipv6 \
                --with-jpeg-dir=/usr \
                --disable-json \
                --with-kerberos=/usr \
                --with-layout=GNU \
                --with-libxml-dir=/usr \
                --enable-mbstring \
                --with-mcrypt=/usr \
                --with-mime-magic=$(MAGIC_MIME) \
                --enable-memory-limit \
                --with-mhash=/usr \
                --with-mm \
                --without-mssql \
                --with-mysql=/usr \
                --with-mysql-sock=/var/run/mysqld/mysqld.sock \
                --with-mysqli=/usr/bin/mysql_config \
                --without-odbc \
                --with-openssl=/usr \
                --with-pcre-regex=/usr \
                --disable-pdo \
                --without-pdo-dblib \
                --with-pear=/usr/share/php \
                --with-png-dir=/usr \
                --with-pspell=/usr \
                --disable-reflection \
                --with-regex=php \
                --disable-rpath \
                --enable-session \
                --enable-shared \
                --enable-shmop \
                --disable-short-tags \
                --disable-simplexml \
                --disable-soap \
                --disable-sockets \
                --without-sqlite \
                --enable-static \
                --with-system-tzdata \
                --without-sybase-ct \
                --enable-sysvsem \
                --enable-sysvshm \
                --enable-sysvmsg \
                --without-tidy \
                --disable-tokenizer \
                --enable-track-vars \
                --disable-trans-sid \
                --with-ttf=/usr \
                --without-unixODBC \
                --disable-wddx \
                --disable-xmlreader \
                --without-xmlrpc \
                --disable-xmlwriter \
                --enable-zip \
                --with-zlib \
                --with-zlib-dir=/usr


To use --with-mm for sessions you will need the libmm14 and libmm-dev packages. --enable-inline-optimization murders performance - don't bother with it. Some might suggest --with-pic for added security but it also slows things down significantly. As you might guess, I tend to go with the "Disable it until I find out I need it." policy of php includes.




PHP Configuration

The master php.ini:

[php]

; We are not Apache. We do not run php4, we do not want short tags, we don't want asp compatibility...
engine                       = Off
zend.ze1_compatibility_mode  = Off
short_open_tag               = Off
asp_tags                     = Off
y2k_compliance               = On
implicit_flush               = Off
safe_mode                    = Off
safe_mode_gid                = Off
safe_mode_include_dir        =
safe_mode_exec_dir           =
safe_mode_allowed_env_vars   = PHP_
safe_mode_protected_env_vars = LD_LIBRARY_PATH
open_basedir                 =

realpath_cache_size          = 16k
realpath_cache_ttl           = 120
expose_php                   = Off
max_input_nesting_level      = 64
memory_limit                 = 128M

report_memleaks              = On
report_zend_debug            = 0

docref_root                  =
docref_ext                   =

; EGPCS is default.
variables_order              = "EGPCS"
register_globals             = Off
register_long_arrays         = Off
register_argc_argv           = Off
auto_globals_jit             = On
; memory_limit needs to be 3-4 times this size in order to accomodate.
post_max_size                = 16M
; Magic quotes just needs to die already.
magic_quotes_gpc             = Off
magic_quotes_runtime         = Off
magic_quotes_sybase          = Off

; Uncomment if you want to override user settings.
;auto_prepend_file            =
;auto_append_file             =

;
user_dir                     =
enable_dl                    = Off

cgi.force_redirect           = 1
cgi.nph                      = 0
cgi.redirect_status_env      =
cgi.fix_pathinfo             = 1
fastcgi.logging              = 1
cgi.rfc2616_headers          = 0

; This is even dumber than php's safe mode.
sql.safe_mode                = Off

; Since the actual limit is set in our apache config, no reason to let the user up it.
upload_max_filesize          = 16M

allow_url_include            = Off

define_syslog_variables      = Off

sendmail_path                = "/usr/sbin/sendmail -t -i"
mail.add_x_header            = 0

[Pcre]
; backtrack_limit may need to be increased for handling very large strings,
; however this does not apply to the forums we're running here.
pcre.backtrack_limit         = 100000
pcre.recursion_limit         = 100000

[MySQL]
mysql.allow_persistent = On
mysql.max_persistent   = -1
mysql.max_links        = -1
mysql.default_port     = 3306
mysql.default_socket   = "/var/run/mysqld/mysqld.sock"
mysql.default_host     = localhost

[MySQLi]
mysqli.max_links       = -1
mysqli.default_port    = 3306
mysqli.default_socket  = "/var/run/mysqld/mysqld.sock"
mysqli.default_host    = localhost
mysqli.reconnect       = On

[Session]
; mm = shared memory.
session.save_handler = files

; This is not necessary.
;session.save_path  = /var/lib/php5

session.use_cookies             = 1
; If you're a bank or providing something over SSL only you may want to toggle this or move it to the user side.
; But in that case if your CMS relies on sessions please let me know so I won't bank with you.
session.cookie_secure           = 0
session.use_only_cookies        = 1
session.auto_start              = 0
session.cookie_lifetime         = 0
session.cookie_httponly         = 1
session.bug_compat_42           = 0
session.bug_compat_warn         = 1
session.use_trans_sid           = 0
; DIE DIE DIE >_>
url_rewriter.tags               = ""

; Messing with these only causes headaches : /
session.serialize_handler       = php
session.entropy_length          = 0
session.entropy_file            =
session.cache_limiter           = nocache
session.cache_expire            = 24
session.hash_function           = 0
session.hash_bits_per_character = 4

; Garbage collection shouldn't be needed with mm.
;session.gc_probability = 0
session.gc_divisor        = 100
session.gc_maxlifetime    = 10800

# configuration for php imagick module
extension=imagick.so

# configuration for php APC module
extension=apc.so

apc.shm_segments           = 1
apc.shm_size               = 64
apc.optimization           = 0
apc.enable_cli             = 0
; I thought this would save me, I was wrong : /
apc.mmap_file_mask         =
apc.slam_defese            = 0
apc.write_lock             = 1
apc.coredump_unmap         = 0

; configuration for php suhosin module
extension=suhosin.so

[suhosin]
; Many of these will end up going to the per-user configuration.
; Logging Configuration
suhosin.log.syslog.facility     = LOG_LOCAL0
suhosin.log.sapi                = S_ALL & ~S_SQL
suhosin.log.use-x-forwarded-for = off
suhosin.log.syslog              = S_ALL & ~S_SQL

; Misc Options
suhosin.executor.allow_symlink  = off
; Begin with this on. See what you need to change, then turn it off.
suhosin.simulation              = on
suhosin.apc_bug_workaround      = off
suhosin.sql.bailout_on_error    = off
suhosin.sql.user_prefix         =
suhosin.sql.user_postfix        =
suhosin.multiheader             = off
suhosin.mail.protect            = 1
suhosin.memory_limit            = 0

; Transparent Encryption Options
suhosin.session.encrypt         = on
; DO NOT LEAVE CRYPTUA ON. There really are browsers and proxy systems
; that are too retarded to use a consistant user agent. SBC Yahoo, I am
; looking at you.
suhosin.session.cryptua         = off
suhosin.session.cryptdocroot    = on
suhosin.session.cryptraddr      = 0
suhosin.session.checkraddr      = 0
suhosin.cookie.encrypt          = on
suhosin.cookie.cryptua          = off
suhosin.cookie.cryptdocroot     = on
suhosin.cookie.cryptraddr       = 0
suhosin.cookie.checkraddr       = 0

; Filtering Options
suhosin.upload.disallow_elf   = on
suhosin.session.max_id_length = 128


The local php.ini:

; I like to set this to 18 to display full double precision.
precision        =  12

; As the documentation says, setting this off should be experimented with.
allow_call_time_pass_reference = On

; If someone really wants to set these for their own site.
output_handler               =
unserialize_callback_func    =

; If we really want.
auto_prepend_file            =
auto_append_file             =

; This is best set in-script.
output_buffering = Off

; I trust the webserver to be more efficient at doing this outright, but more
; importantly this sort of thing needs a far more customized touch than a
; single setting provides.
zlib.output_compression = Off
;zlib.output_compression_level = -1
;zlib.output_handler =

; This does not need to be 100, double doesn't resolve past 18.
serialize_precision        = 24

; May as well let these be configured per-site.
disable_functions =
disable_classes   =

; Colors for Syntax Highlighting mode.  Anything that's acceptable in
; <span style="color: ???????"> would work.
;highlight.string  = #DD0000
;highlight.comment = #FF9900
;highlight.keyword = #007700
;highlight.bg      = #FFFFFF
;highlight.default = #0000BB
;highlight.html    = #000000

; These would get edited for site upgrades.
ignore_user_abort  = Off
max_execution_time = 45
max_input_time     = 60

; Error stuffs!
error_reporting        = E_ALL
display_errors         = On
display_startup_errors = On
log_errors             = On
log_errors_max_len     = 0
ignore_repeated_errors = On
ignore_repeated_source = Off

; Store the last error/warning message in $php_errormsg (boolean).
track_errors           = On

; Disable the inclusion of HTML tags in error messages.
html_errors            = Off

; String to output before an error message.
;error_prepend_string = "<font color=#ff0000>"

; String to output after an error message.
;error_append_string = "</font>"

error_log = /home/elliuser/logs/php.log

; Messing with these would be an interesting exercise for a custom site.
;arg_separator.output = "&amp;"
;arg_separator.input = ";&"

default_mimetype = "text/html"
;default_charset = "iso-8859-1"

; Always populate the $HTTP_RAW_POST_DATA variable.
;always_populate_raw_post_data = On

doc_root      =

include_path  = ".:/usr/share/php:/usr/share/pear"
extension_dir = "/usr/lib/php5/20060613"

file_uploads                 = On

; Random idea would be to actually set a per-account temporary directory.
upload_tmp_dir               = /tmp

allow_url_fopen              = On

; Ah the advantages of user-specific configuration files...
;from                         = "ellimailer@elliquiy.com"
;user_agent                   = "The Elliquiy Browser"

default_socket_timeout       = 60

auto_detect_line_endings     = Off

; If you want to use this function you can grab the ini file from here:
; http://browsers.garykeith.com/downloads.asp
browscap                     = /etc/php5/browscap.ini

;sendmail_from                = "ellimailer@elliquiy.com"
mail.force_extra_parameters  =

; This belongs in userspace.
assert.active                = Off
assert.warning               = On
assert.bail                  = Off
assert.callback              = 0
assert.quiet_eval            = 0

[Date]
; Defines the default timezone used by the date functions
;date.timezone =

;date.default_latitude = 31.7667
;date.default_longitude = 35.2333

;date.sunrise_zenith = 90.583333
;date.sunset_zenith = 90.583333

[exif]
;exif.encode_unicode = ISO-8859-15
;exif.decode_unicode_motorola = UCS-2BE
;exif.decode_unicode_intel    = UCS-2LE
;exif.encode_jis =
;exif.decode_jis_motorola = JIS
;exif.decode_jis_intel    = JIS

[gd]
; Tell the jpeg decode to libjpeg warnings and try to create
; a gd image. The warning will then be displayed as notices
; disabled by default
;gd.jpeg_ignore_warning = 0

[mbstring]
;mbstring.language = English
;mbstring.internal_encoding = UTF-8
;mbstring.http_input = auto
;mbstring.http_output = UTF-8
;mbstring.encoding_translation = Off
;mbstring.detect_order = auto
;mbstring.substitute_character = none;
;mbstring.func_overload = 0

[MySQL]
; Normally, entering a username and password here would be a bad idea.
; Normally. This file is not readable by anyone but root or the user,
; which may or may not be the case for a given .php file.
mysql.default_user     =
mysql.default_password =
mysql.connect_timeout  = 60
mysql.trace_mode       = On

[MySQLi]
mysqli.default_user    =
mysqli.default_pw      =

[Session]
session.name          = PHPSESSID
session.cookie_path   = /
session.cookie_domain =
session.referer_check =

# User APC configuration
apc.enabled                = 1
apc.cache_by_default       = 1
apc.file_update_protection = 1
apc.filters                =
apc.include_once_override  = 0
apc.localcache             = 0
apc.localcache.size        = 1024
apc.max_file_size          = 1M
apc.num_files_hint         = 2048
apc.report_autofilter      = 0
apc.stat                   = 1
apc.stat_ctime             = 0

apc.rfc1867                = 0
apc.rfc1867_prefix         = "upload_"
apc.rfc1867_name           = "APC_UPLOAD_PROGRESS"
apc.rfc1867_freq           = 0

apc.ttl                    = 86400
apc.gc_ttl                 = 3600
apc.user_ttl               = 86400

[suhosin]
; Logging
suhosin.log.syslog.priority     = LOG_DEBUG
suhosin.log.script              = 0
suhosin.log.phpscript           = 0
suhosin.log.script.name         =
suhosin.log.phpscript.name      =
suhosin.log.use-x-forwarded-for = off

; Executor Options
suhosin.executor.max_depth             = 0
suhosin.executor.include.max_traversal = 3
suhosin.executor.include.whitelist     =
suhosin.executor.include.blacklist     =
suhosin.executor.func.whitelist        =
suhosin.executor.func.blacklist        =
suhosin.executor.eval.whitelist        =
suhosin.executor.eval.blacklist        =

; Going to basically begin with auditing code.
; As with disabling call time pass by reference I
; want to also test these carefully and per site.
suhosin.executor.disable_eval          = off
suhosin.executor.disable_emodifier     = off

; Suhosin encryption fun. Set the keys per-user.
suhosin.session.cryptkey        = "abcdefgh"
suhosin.cookie.cryptkey         = "abcdefgh"
suhosin.cookie.cryptlist        =
suhosin.cookie.plainlist        =

; Filtering Options
suhosin.filter.action =
suhosin.cookie.max_array_depth = 100
suhosin.cookie.max_array_index_length = 64
suhosin.cookie.max_name_length = 64
suhosin.cookie.max_totalname_length = 256
suhosin.cookie.max_value_length = 10000
suhosin.cookie.max_vars = 100
suhosin.get.max_array_depth = 50
suhosin.get.max_array_index_length = 64
suhosin.get.max_name_length = 64
suhosin.get.max_totalname_length = 256
; SMF can have some pretty long GETs, so I increased this over Suhosin's default.
suhosin.get.max_value_length = 1024
suhosin.get.max_vars = 100
suhosin.post.max_array_depth = 100
suhosin.post.max_array_index_length = 64
suhosin.post.max_name_length = 64
suhosin.post.max_totalname_length = 256
; You may want to increase this and request.max_value_length to enable larger POST sizes, or otherwise tune this per-site.
suhosin.post.max_value_length = 262144
suhosin.post.max_vars = 200
suhosin.request.max_array_depth = 100
suhosin.request.max_array_index_length = 64
suhosin.request.max_totalname_length = 256
suhosin.request.max_value_length = 262144
suhosin.request.max_vars = 200
suhosin.request.max_varname_length = 64
suhosin.upload.max_uploads = 25
suhosin.upload.disallow_binary = off
suhosin.upload.remove_binary = off
suhosin.upload.verification_script =
suhosin.cookie.disallow_nul   = on
suhosin.get.disallow_nul      = on
suhosin.post.disallow_nul     = on
suhosin.request.disallow_nul  = on


The flaw in this arrangement is that you will have to stress test sites a bit after turning suhosin simulation off.

I think Suhosin is a rather powerful development tool for php, but again, discussing that is outside the scope of this document : )