Chris Hope's LAMP Blog - The Electric Toolbox

Linux Apache MySQL and PHP articles by Chris Hope

This is Chris Hope's blog for Linux, Apache, MySQL and PHP (known as LAMP) and Javascript/jQuery. I started this website several years ago with articles about web programming, Linux and Windows tips and tricks, howtos etc.

The ten most recent articles can be found below in their entirety. Navigating the sections in the right navigation (under Categories) will bring up all the other posts, and you can also use the search box at the top of the page to find what you might be looking for.

View the message header and body for an email in the exim mail queue

Posted February 12th, 2016 in Email Servers

You've checked to see what's sitting in the exim mail queue, but want to check the email's header and/or body before deleting it from the queue. Use the -Mvh & -Mvb flags to view these.

Getting the message id

Use "exim -bp" or "mailq" to see what's in the queue to get the message id; you'll need it when you want to view the header or body.

$ exim -bp
 1m   321 1aU276-0001cf-DR 

In the above example, 1aU276-0001cf-DR is the message id

Viewing the email header with exim

Run "exim -Mvh [message id]" to see the headers ("h" for header), e.g. using the id above:

$ exim -Mvh 1aU276-0001cf-DR

Viewing the email body with exim

Run "exim -Mvb [message id]" to see the headers ("b" for body), e.g. using the id above:

$ exim -Mvb 1aU276-0001cf-DR

RamNode cheap virtual servers

Posted February 6th, 2016 in Linux/Unix/BSD

I needed a really cheap standalone virtual server to test some stuff, on the internet and not running behind a VDSL router at my office, and got the cheapest RamNode server for $13.50 for a whole year.

RamNode servers

I honestly have no idea if RamNode are any good or not (my own websites are hosted using VPS servers at Linode), but for what I need this is perfect: it's more or less throw away money, and I get a server with a static IP address I can switch on, test some stuff and switch back off again as and when needed.

What am I testing?

I'm testing out the free Let's Encrypt secure certificates. These are domain validated certificates: validation is done by a certificate management agent on the web server, which receives requests from the LE servers to prove you manage the domain. I'll talk more about this in a future post, but suffice to say for the moment that I didn't want to run this on a production server (yet).

Get it here

Their cheapest one has 128MB of RAM, 12GB of storage and 500GB of bandwidth, running OpenVZ - for $15 for a year. You can get a 10% discount using the SSD10 coupon code. You can order it here and/or check out the other plans.

I am not affiliated with RamNode in any way, and the above link is not an affiliate link. I found out about them from the comments in this article at Ars Technica.

nslookup: command not found error on Debian

Posted February 5th, 2016 in Linux/Unix/BSD

If you need to use nslookup on a Debian server and you're getting an "nslookup: command not found" then use apt-get to install dnsutils.

Error message

You'll know if dnsutils & nslookup are not installed if you try a dns lookup and get this error:

$ nslookup
-bash: nslookup: command not found

Install dnsutils

Install the dnsutils package to resolve the error and lookup the domain:

$ apt-get install dnsutils

And now it works

$ nslookup

Non-authoritative answer:

Base64 decode from the command line

Posted December 17th, 2015 in Linux/Unix/BSD

I have some automated processes that receive updates via email attachments which are base64 encoded. The entire email is backed up into a text file, and very occasionally I need to check what was in an attachment. The command line base64 tool can help with this, either decoding a file or standard input.

Using base64 to decode a file

The -d or --decode flag tells base64 it's decoding data (on a Mac -d is a debugging flag, so it's -D and --decode instead). In the examples below, I've used --decode, but you can use -d or -D instead.

To decode the file at /tmp/encoded.txt do this:

base64 --decode /tmp/encoded.txt

And the decoded file will be written to standard output. If you wanted to write it to a file, you can redirect it like this, where it's written out to /tmp/decoded.txt

base64 --decode /tmp/encoded.txt > /tmp/decoded.txt

Using base64 to decode some text

If you run base64 --decode without a file, you can type text (or copy and paste it), hit return/enter, and then control+d / ctrl+d and it will be decoded.

So, for example, to decode VGhpcyBpcyBiYXNlNjQgZW5jb2RlZAo=, which is "This is base64 encoded" encoded as base64, do this:

base64 --decode

It will then be written to standard output. Again, you can redirect the output to a file:

base64 --decode > /tmp/decoded.txt

Using pbpaste on a Mac

If you have the encoded text in your clipboard, you can use pbpaste to pipe it through base64 instead of having to manually paste in the terminal. There are equivalent commands when using X (xclip) but I've never used these myself.

Using pbpaste:

pbpaste | base64 --decode

Install Java on the command line with Debian

Posted December 16th, 2015 in Linux/Unix/BSD

If it was nice and intuitive to install Java on Debian (and other Debian based distros such as Ubuntu), I wouldn't need to write this post because it would just be "apt-get install java". But no, I always forget what I need to install, hence this post...

Install openjdk-7-jre or openjdk-7-jre-headless

If you just want to install the runtime so you can run Java applications, install either openjdk-7-jre or openjdk-7-jre-headless. The first one will install a bunch of Gnome stuff if not already installed, so if you are doing this on a server without a GUI then install openjdk-7-jre-headless.

sudo apt-get install openjdk-7-jre


sudo apt-get install openjdk-7-jre-headless

Right, now maybe I'll remember this next time :)

This is correct for (at least) Debian 7 & 8, possibly older versions, probably future versions, and possibly other Debian based distributions.

Discard mail with exim4 using :blackhole:

Posted December 15th, 2015 in Email Servers

The most obvious way to discard mail to a particular alias with the exim4 mail server is to send it to /dev/null, but unless file_transport is set in the exim configuration it won't work. You can use :blackhole: instead.

/etc/aliases with /dev/null

To route mail to a particular address (e.g. to /dev/null, you would add this to the /etc/alises file:

noreply: /dev/null

If file_transport isn't configured, you'll get error messages like this in the log:

2015-12-15 15:36:14 1a8fTK-0003ip-Lp DKIM: s=20150623 c=relaxed/relaxed a=rsa-sha256 [verification succeeded]
2015-12-15 15:36:14 1a8fTK-0003ip-Lp <= [] P=esmtp S=3709
2015-12-15 15:36:14 1a8fTK-0003ip-Lp == /dev/null <> R=system_aliases defer (-30): file_transport unset in system_aliases router

Although there's an error, the mail isn't bounced back to the sender immediately, and sits undelivered in the mail queue (and presumably will bounce back eventually).

Use :blackhole: instead

Without having to make any configuration changes to exim, you can instead route the mail to :blackhole: - change the line in /etc/alises to this:

noreply: :blackhole:

And then you'll get this in your log file:

2015-12-15 15:39:27 1a8fWR-0003jO-M3 DKIM: s=20150623 c=relaxed/relaxed a=rsa-sha256 [verification succeeded]
2015-12-15 15:39:27 1a8fWR-0003jO-M3 <= [] P=esmtp S=3702
2015-12-15 15:39:27 1a8fWR-0003jO-M3 => :blackhole: <> R=system_aliases
2015-12-15 15:39:27 1a8fWR-0003jO-M3 Completed

The mail is now "delivered" and there are no errors in the log file.

Work out PHP's error reporting from an integer value

Posted December 14th, 2015 in PHP

This post has some example PHP code which can be used to work out which error_reporting levels are reported and which are not from an integer value.

PHP code

Either use the $error = error_reporting() line to work out the error reporting levels for the current setting, or use a hard-coded value, replacing 22527 in the example code with the value you want to decode. Replace \n with <br> tags if it's being rendered on a webpage, or save the output to a variable if writing to file or emailing etc.

//$error = error_reporting();
$error = 22527;

$levels = array(

echo "Report on:\n";
foreach ($levels as $level => $description) {
	if ($level & $error) {
		echo "$description\n";

echo "\n";

echo "Do not report on:\n";
foreach ($levels as $level => $description) {
	if ($level & ~$error) {
		echo "$description\n";

Example output

Report on:

Do not report on:

Implement placeholders in IE8 & 9 (and other older browsers) with jQuery

Posted December 10th, 2015 in HTML and CSS and Javascript

HTML5 saw the introduction of the very useful placeholder attribute for input elements, but it's not supported in Internet Explorer before version 10. There are still a number of people out there with version 8 & 9 but fortunately there are some ways to support placeholders in these older browsers with Javascript.

The old way

Before placeholders, I used to toggle default text on and off using Javascript and used an alternative field for dealing with password fields (here and here). You could still use these older methods for browsers that don't support placeholders but I found a much simpler way to do it today.

Implementing placeholders in older browsers

I don't claim any credit for the code on this page, I've just assembled it together and tested it on IE8 & IE9 and made sure it doesn't run on modern browsers.

The testing for placeholders code comes from here.
The jQuery placeholder code comes from here.

You may want to change it to suit your own use-cases or other functions you might have for detecting whether placeholders are supported (e.g. with Modernizr) or use vanilla Javascript instead of jQuery.

function placeholderIsSupported() {
	var test = document.createElement('input');
	return ('placeholder' in test);

if(!placeholderIsSupported()) {
	$('[placeholder]').focus(function () {
		var input = $(this);
		if (input.val() == input.attr('placeholder')) {
	}).blur(function () {
		var input = $(this);
		if (input.val() == '' || input.val() == input.attr('placeholder')) {

Note that the password placeholder will still be masked and will not show whatever text you have put in there as a placeholder (.e.g placeholder="password"). 

Google remarketing tag iframe height issue

Posted December 9th, 2015 in HTML and CSS

Google's remarketing code inserts an iframe (with no content) directly below the opening <body> tag, which is 300x13px and can throw out a website's layout. This post looks at how to effectively hide it with CSS in a couple of different ways that hopefully shouldn't mess with its functionality at all.

Using position: absolute

Add this to your CSS and it will use absolute positioning, pushing it off the left side of page so it cannot be seen and doesn't affect content flow:

iframe[name='google_conversion_frame'] {
    position: absolute;
    left: -1000px;

Setting the height

Another method is to set the height instead:

iframe[name='google_conversion_frame'] {
    height: 0;

I've seen posts that suggest setting display: block as well, but just setting the height seems to work OK. If you find it's not setting the hide in a particular browser, then try adding display block too.

Setting display: none?

This will work to hide the frame, but it's probably not a good idea because it may mess with the functionality of the remarketing code. Obviously the above options may too, but something needs to be done to prevent it from pushing layouts out.

Send CSV data to the browser from MySQL & PHP with fputcsv

Posted December 8th, 2015 in MySql and PHP

I covered how to use PHP's fputcsv without writing to a file in yesterday's post and look at a practical example today: fetching data from MySQL using PDO and sending it to the browser as a CSV, using the fputcsv() function.

Example data

The data used in this post is from my example table for MySQL post.

Example code

Substitute the [database name], [username] and [password] fields with the appropriate credentials and name for your database, and modify the database query to suit your use case. If you are using a different library than PDO, then obviously you'll need to change the database function calls to use that library instead.

ini_set('display_errors', 1);

$dsn = "mysql:host=localhost;dbname=[database name]";
$username = "[username]";
$password = "[password]";

try {
    $pdo = new PDO($dsn, $username, $password);
} catch (PDOException $e) {
    // error handler

header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="filename.csv"');

try {
    $stmt = $pdo->prepare("SELECT * FROM fruit ORDER BY name, variety");
    $output = fopen('php://output', 'w');
    $header = true;
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        if ($header) {
            fputcsv($output, array_keys($row));
            $header = false;
        fputcsv($output, $row);
} catch (PDOException $e) {
    // error handler

The example code writes out a header line containing the column names, and then one line for each record.

If you prefer to write the data out to a file, remove the two header lines and change php://output to the filename.

I'll cover how to buffer it into memory instead (for e.g. attaching to an email without having to write it out to file) in a subsequent post.

Example output

Example output from the fruit table:

6,Apple,"Cox's Orange Pippin"
7,Apple,"Granny Smith"
1,Apple,"Red Delicious"