The Smashing Book – Performance Optimization for Websites (part 2 of 2)

← Performance Optimization for Websites (1/1)TOC

Server Section

Now things get technical. You will require root-level access to your server machine. If you are in a shared-hosting environment, you may not have this level of access. This section will still be useful to you because you can check if your Web host’s machine meets your requirements, and if it does not, you will know what to demand from your host.

Tuning a Web server requires low-level access to the machine, preferably via SSH. Therefore, you should have basic knowledge of how to log on using SSH, how to change configuration files as root and how to restart services. You probably will need to refer to your Web server’s documentation from time to time to implement these tips. Explaining how exactly to do things in detail is impossible here because Web server software is heterogeneous. Things may get done differently depending on whether you use Debian GNU/Linux, Windows, Red Hat GNU/Linux, etc.

We will assume a standard Debian GNU/Linux (“Lenny”) as the server operating system here.

Apache: How to set up expires header

An “expires header” is an HTTP header attribute that indicates how long a file should be considered “fresh”. Browsers do not request a file again as long as it is cached and has not expired, which reduces the number of HTTP requests.

Let’s fix that. Log on to your Web server using SSH and become root:

su

Optionally, you may now install an easy-to-use text editor by issuing

aptitude install nano

at the command prompt. Let’s continue by enabling Apache’s expire module:

a2enmod expires

Make sure that parsing of .htaccess files is enabled. Edit your website’s configuration file. The file name will vary. It should be located in "/etc/apache2/sites-enabled". Let’s assume that the file name is “000-default” and that your preferred text editor is “nano”:

nano /etc/apache2/sites-enabled/000-default

If “AllowOverride” has already been set to “All”, leave it this way and quit. Otherwise, change “AllowOverride none” to “AllowOverride All”. This is the most common setting. Save the file and exit. Then check Apache’s configuration file:

apache2ctl -t

If everything went fine, restart and exit:

/etc/init.d/apache2 restart && exit

Then switch to your FTP or SCP client. Go to the wwwroot of your website and create a new file called “.htaccess”. Notice the preceding dot. Load it in your desktop text editor, and enter the following lines:

ExpiresActive On
ExpiresDefault "access plus 1 year"
ExpiresByType text/html "access plus 1 hour"

This will make most files expire one year after being requested, while HTML pages will stay fresh for one hour. Rule of thumb: let static files stay fresh for a long time. You can experiment, setting different values for different content types, such as “text/javascript”, “application/javascript”, “text/css”, “image/gif”, “image/png”, “image/jpg” and so on.

Use YSlow to check if the expires headers get set according to the new configuration. Clear your browser cache first.

100000000000021E0000029C1170FB19.png
YSlow is complaining here about missing expire headers.

If you have set up expire headers, visitors to your website might still be using an old cached version of your CSS or JavaScript file, despite the fact that you had updated them before they expired. There is a simple workaround. Append a version parameter to the CSS and JavaScript links, like so:

<link rel="stylesheet" type="text/css" href="style.
css?v=1.0" />

Naturally, the parameter will not be read or processed anywhere. It serves merely as a indicator to browsers. When you change the file, simply update the parameter, for example, from “v=1.0” to “v=1.1”, so that browsers fetch the updated version.

Lighttpd: How to set up expires header

Lighttpd, or Lighty, does not read .htaccess files. So, we have to put everything into Lighty’s configuration files. Become root and go to /etc/lighttpd/conf-available:

su
cd /etc/lighttpd/conf-available

Create a new configuration file that accommodates the module’s configuration.

nano ./01-expire.conf

For the sake of simplicity, we will set expire settings globally for all virtual hosts (vhosts). Put these lines into the newly created file:

server.modules += ( "mod_expire" )
$HTTP["url"] =~ "" {
expire.url = ( "" => “access 12 hours" )
$HTTP["url"] =~ "(7z|rar|zip|gz|pdf|exe|css|js|png|gif|
jpg|ico)$" {
expire.url = ( "" => "access 1 years" )
}

This configuration will push expire dates one year into the future for static files. Other content will expire one hour from being accessed. Save the file and exit. We now have to incorporate the new file into the current configuration and restart Lighty:

lighttpd-enable-mod expire
/etc/init.d/lighttpd force-reload

100000000000023F0000024C9BD3D1C0.png
Lightly is now giving expire dates

Apache: How to set up ETags

An ETag (short for “entity tag”) is an HTTP response header used to determine changes in a given file’s content. When a new HTTP response contains the same ETag as an older HTTP response, their contents are considered to be the same without further downloading.

In Apache, file entity tags are set to “All”. That means, an ETag is generated from the Inode, Mtime and file size information of a file. If your Web server does not provide ETags, you may have to enable them manually. Edit the .htaccess file of your website and search for “FileETag” directives. You may find something like “FileETag None”. Change it to:

FileETag All

Save and upload the file. Then check whether Apache now provides ETags. If it does not, Apache may have been compiled with ETags disabled. You may have to switch to another Apache version or ask your Web host to enable this feature.

Lighttpd: How to set up ETags

Lighty on Debian defaults to ETags enabled, so no action is required. Nevertheless, this is how you would enable them. Become root:

su
cd /etc/lighttpd/conf-available

Create a new configuration file, in which we will enable ETags:

nano ./10-etags.conf

Put this line into the newly created file:

static-file.etags = !"enable"

Save the file and exit. Then make the new file current and restart Lighty:

lighttpd-enable-mod etags
/etc/init.d/lighttpd force-reload

Installing eAccelerator for PHP

Debian does not come with pre-built eAccelerator packages. We will have to compile them from source. We will assume your Web server has Debian Lenny and PHP 5 already up and running. We have to download some packages first:

aptitude install bzip2 php5-dev make

Become root and pull the source from the eAccelerator website:

su
cd /tmp
wget http://bart.eaccelerator.net/source\
/0.9.5.3/eaccelerator-0.9.5.3.tar.bz2

The current version as of March 2009 is 0.9.5.3. You may want to check for the latest version. Replace the version string accordingly.

Unpack the archive and change the directory:

tar xvjf eaccelerator-0.9.5.3.tar.bz2
cd eaccelerator-0.9.5.3

Start compiling right away:

export PHP_PREFIX="/usr"
$PHP_PREFIX/bin/phpize
./configure –enable-eaccelerator=shared \
–with-php-config=$PHP_PREFIX/bin/php-config
make && make install

eAccelerator’s configuration file needs to be created manually:

nano /etc/php5/conf.d/ea.ini

Copy the following lines into the file and then save:

[eAccelerator]
extension="eaccelerator.so"
eaccelerator.shm_size="32"
eaccelerator.cache_dir="/tmp"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"

10000000000002B80000029A6113A1BC.png
If eAccelerator has been set up correctly, there will be an extended copyright note.

Depending on how your Web server incorporates PHP, you may have to restart:

/etc/init.d/lighttpd restart

or

/etc/init.d/apache2 restart

Let’s check if eAccelerator has been compiled and installed successfully. Switch to your FTP or SCP client. Go to the wwwroot of your website and create a file called “inf.php”. Put this line in it:

<?php phpinfo(); ?>

Save, close and upload the file. Go to your Web browser and request the file:

http://www.example.com/inf.php

Obviously, you will have to replace www.example.com with the actual host name.

Do not forget to remove packages that you do not need to run the server:

aptitude remove php5-dev make

Important: Remove inf.php if it is publicly accessible. It could provide useful information for hackers, and you certainly do not want to help them break into your system.

MySQL Tuning Primer

Once again, tuning MySQL requires SSH access. You will also need a MySQL account that is allowed to access runtime information (“root”, for example, not to be confused with the server’s root account) and an up-and-running MySQL server, of course. Log in using SSH and fetch MySQL Tuning Primer:

wget http://launchpad.net/mysql-tuning-primer/trunk\
/1.5-r1/+download/tuning-primer.sh

Make it executable:

chmod +x ./tuning-primer.sh

You may need to resolve a dependency for the script. For its calculations, it relies on “bc”, a small command-line calculator:

aptitude install bc

Launch the script and provide MySQL account information. MySQL Tuning Primer will now analyze runtime information and provide tips on optimizing your MySQL configuration.

Read these tips carefully and change MySQL’s configuration accordingly:

nano /etc/mysql/my.conf

For example, if Tuning Primer suggests that “Your query_cache_size seems to be too high. Perhaps you can use these resources elsewhere”, then edit the configuration file as stated above. Search for “query_cache_size” and lower its value by roughly 10%. Do not change configuration values too much in one shot, but rather do it in 10 to 20% increments. Save and exit.

Restart MySQL:

/etc/init.d/mysql restart

We recommend waiting 48 hours before starting another tuning session to let MySQL accumulate meaningful statistics.

Enabling Transparent Compression

If you run a low-traffic website, with either few visitors or small HTML, CSS and JavaScript files, compressing files may not be feasible, because compressing on the fly means that an additional load could push your server over the edge when it has to cope with a critical load level.

However, if you do have left-over resources and large text files (such as HTML, CSS, JavaScript and JSON), you may want to enable compressing, because it can save bandwidth and reduce transmission times.

Enabling compression for Apache

This is how you enable compression for Apache. Become root and edit deflate.conf

su
nano /etc/apache2/mods-available/deflate.conf

By default, mod_deflate for Apache in Debian Lenny will apply compression for three content types: “text/html”, “text/plain” and “text/xml”. But this is insufficient. You may want to extend the list of content types to include CSS, JavaScript and maybe even JSON, ATOM and RSS as well. Deflate.conf should then look like this:

<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/
x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE text/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/rdf+xml
AddOutputFilterByType DEFLATE application/atom+xml
# Netscape 4.x has some problems...
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip
# MSIE masquerades as Netscape, but it is fine
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
</IfModule>

Commented lines have been taken from the official mod_deflate documentation.

You may be tempted to enable compression globally for all file types, because browsers tend not to request (or accept) content encoding for images. Practically speaking, serious issues could arise when browsers request compressed content for large files that are already compressed, possibly leading to a DDOS-type situation. This is why we have left out images, video and other such content types, because these files are usually already compressed, and even if browsers do request them compressed (due to an error or hacking), the Web server will not compress them.

Enabling compression for Lighttpd

Compression is enabled by default in Lighttpd for Debian Lenny, so there should be no need to enable it manually. Even so, knowing how to enable compression might come in handy in future.

Become root and go to /etc/lighttpd/conf-available:

su
cd /etc/lighttpd/conf-available

Create a new configuration file, in which we will enable the module:

nano ./05-compress.conf

Put the following line in the newly created file:

server.modules += ( "mod_compress" )

Save the file and exit. Incorporate the new file into the current configuration and restart Lighty:

lighttpd-enable-mod compress
/etc/init.d/lighttpd force-reload

Enabling compression for PHP

You may want to enable transparent output compression for PHP scripts as well. Become root by issuing “su”. Then edit php.ini. The path depends on which Web server you are using and how it incorporates PHP. For Apache and mod_php, it would be:

nano /etc/php5/apache2/php.ini

10000000000002F9000002BF48E9F306.png
Already compressed files should not be compressed again.

For Lighttpd and FastCGI, it would be:

nano /etc/php5/cgi/php.ini

Search for “zlib.output_compression” and enable it:

zlib.output_compression = On

Save and exit. Restart the Web server by either

/etc/init.d/lighttpd restart

or

/etc/init.d/apache2 restart

Verifying compression

Once again, go to Firebug’s Net tab and reload your website. HTML, Java-Script, CSS, XML, RSS, ATOM, JSON and plain text content should now be compressed by default, while everything else should not be.

Avoid Redirects When Possible

Websites that have evolved over time often have many redirects. Files get moved, become obsolete and vanish. Redirecting requests for files that have moved or vanished is a good idea because it helps users find what they are searching for. If you install redirects, check whether there is already a redirection chain:

A > B > C > D

In this example, a visitor is requesting file A. The request gets redirected to destination B, then C, then D. This makes for four requests, when two would suffice:

A > D

The benefit: if you carefully optimize redirection chains, your Web server will be able to serve more visitors at once.

How Do You Detect Longer-Than-Necessary Redirection Chains?

Switch to Firefox. Bring up Firebug, and click the Net tab. If disabled, check “Network monitoring” and apply settings. Firebug will then reload the current page and list every request necessary to load the current page.

When a visitor requests an HTML file, Firebug will list every request along with its response code. 302 means a temporary redirect. This example server is not optimized, so you cannot compare absolute numbers with real-world production servers. In this case, four requests take 180 milliseconds to process. This can add up to seconds if a page request leads to more than one long redirection chain. Instead of redirecting indirectly (in an .htaccess file, for example), like so:

Redirect /A.html http://www.example.com/B.html
Redirect /B.html http://www.example.com/C.html
Redirect /C.html http://www.example.com/D.html

you should let visitors take the direct path:

Redirect /A.html http://www.example.com/D.html

A short redirection chain can be processed significantly more quickly than a long chain. In this case, it took 105 milliseconds to redirect the visitor.

100000000000038700000127C7965A2D.png
Avoid unnecessary redirects.

Link Consistently

With permalinks enabled, WordPress automatically redirects URLs without a trailing slash:

http://www.example.com/post -> http://www.example.com/post/

Note the trailing slash. This creates two requests, when one would suffice. Make sure, then, that WordPress permalinks have a trailing slash, which reduces the number of requests necessary to serve a visitor. In other words, if you link internally, make sure the links always have a trailing slash; unless you link to static files, such as *.html or *.php, that is.

By the way, two links should not serve the same content. Make sure that requests to http://www.example.com/postname actually do get redirected to http://www.example.com/postname, not for performance purposes but rather for search engine optimization, because doubled content could lead to ranking penalties.

Avoid Dead Links

Please be careful about this: if a non-existent file is requested, the server must send a 404 header to explicitly tell the client that this file is gone.As a website owner, you should make sure not to link to non-existent HTML, CSS and JavaScript files. Many Web servers log 404 requests in separate log files, and so many (avoidable) 404 requests will bloat your log files, which in turn raises the required server resources (such as CPU cycles and disk space).

How Do You Detect Dead Internal Links?

This is a walk in the park. Let’s assume you have an almost-white background image called “background.png” that you assign via the CSS attribute “background-image” to the body tag:

body { background-image: url("background.png"); }

Let us further assume that the file does not exist. By looking at the Web page, you would not notice the missing background; or at least browsers usually do not explicitly report missing files that are to be loaded via CSS.

Switch to Firefox. Clear the browser’s cache. Bring up Firebug and reload the current page. Firebug will list all requests, including those that come from CSS declarations.

10000000000003870000013AE42513BC.png
Firebug highlights requests that have led to a 404 response.

Firebug highlights requests that have led to a 404 response. Correct this issue by either putting the right file in the right place or by changing or removing the CSS declaration. This way, you use the server’s resources more efficiently.

New Kid On The Block: Page Speed by Google

Page Speed is a tool that Google has been using internally to improve the performance of its Web applications. It’s a Firefox add-on integrated with Firebug, just like YSlow.

While both plug-ins have many similarities, Page Speed provides information that YSlow does not offer:

  • JavaScript events,
  • Display of unused CSS declarations,
  • Display of infficient CSS selectors.

This plug-in is obviously heavily geared to large Web applications that would benefit from such fine-grained optimization. If you are a Web application developer, give it a try.

← Performance Optimization for Websites (1/1)TOC

The Smashing Team loves high-quality content and cares about little details. Through our online articles, books and ebooks and Smashing Conferences, we are committed to stimulating creativity and strengthening the Web design community’s creative forces.

  1. 00

    No comments have been posted yet. Please feel free to comment first!
    Note: Make sure your comment is related to the topic of the article above. Let's start a personal and meaningful conversation!

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top