How to install an SSL certificate for Apache, from start to finish

  1. Create an SSL key to use to generate the certificate signing request

    (Save this, you’ll need it to install the certificate). To generate the keys for the Certificate Signing Request (CSR) run the following command from a terminal prompt:

    openssl genrsa -des3 -out server.key 1024
    Generating RSA private key, 1024 bit long modulus
    .....................++++++
    .................++++++
    unable to write 'random state'
    e is 65537 (0x10001)
    Enter pass phrase for server.key:

    Enter a passphrase.

    Now we’ll remove the passphrase from the key, so that you don’t have to enter this passphrase whenever you restart Apache:

    openssl rsa -in server.key -out server.key.insecure
    mv server.key server.key.secure
    mv server.key.insecure server.key
  2. Generate a certificate signing request

    openssl req -new -key server.key -out server.csr

    It will prompt you to enter Company Name, Site Name, Email Id, etc. Once you enter all these details, your CSR will be created and it will be stored in the server.csr file.

    You can now submit this CSR file to a Certificate Authority (CA) for processing. The CA will use this CSR file and issue the certificate.

  3. Purchase an SSL certificate

    You will be asked to supply the CSR that you generated in #2.

  4. Install the SSL key from #1, the SSL certificate from #3, and the SSL issuer root certificates (aka “bundle” or “chain”).

    On an Ubuntu server, I usually upload the files here:

    /etc/apache2/ssl/domain.com.key
    /etc/apache2/ssl/domain.com.crt
    /etc/apache2/ssl/domain.com.bundle
  5. Modify your Apache vhost

    Note: Apache only supports one SSL vhost per IP address.

    Replace {ip_address} with the public IP address of the server:

    <VirtualHost {ip_address}:443>
        DocumentRoot /var/www/vhosts/domain.com
    
        SSLEngine on
        SSLVerifyClient none
        SSLCertificateFile /etc/apache2/ssl/domain.com.crt
        SSLCertificateKeyFile /etc/apache2/ssl/domain.com.key
        SSLCertificateChainFile /etc/apache2/ssl/domain.com.bundle
    
        <Directory /var/www/vhosts/domain.com>
            AllowOverride All
            order allow,deny
            allow from all
            Options -Includes -ExecCGI
            AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml application/x-javascript
        </Directory>
    </VirtualHost>
  6. Restart Apache

    /etc/init.d/apache2 restart

That’s all!

Cannot run a binary executable from PHP and MAMP?

I recently encountered this obscure error when trying to run an external program using PHP (which was bundled with MAMP Pro) on Mac OS X:

dyld: Symbol not found: __cg_jpeg_resync_to_restart
Referenced from: /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib
Expected in: /Applications/MAMP/Library/lib/libJPEG.dylib
in /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib

Oddly, other commands execute fine from PHP using shell_exec(). Even more strangely, the external program ran fine when executed from a PHP command-line script, just not from MAMP’s bundled version of Apache!

Thanks to this tidbit from the PrinceXML forums, the solution was easy:

MAMP changes the environment variable $DYLD_LIBRARY_PATH. Check the file:

/Applications/MAMP/Library/bin/envvars

and try out if commenting out the two uncommented lines will work for you.

Unshorten URLs with PHP and cURL

While working on a site that provides previews of URLs embedded in tweets using the awesome PhantomJS scriptable WebKit browser, and encountered difficulties when an URL shortener such as Bit.ly was used (as is almost always done when tweeting out a link to an interesting article or photo).

After a little experimenting, I discovered that cURL makes it super easy to un-shorten a URL that has been shortened, without depending on a third-party service.

Improving slightly on this function, here’s how I did it in PHP:

function unshorten_url($url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, array(
        CURLOPT_FOLLOWLOCATION => TRUE,  // the magic sauce
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_SSL_VERIFYHOST => FALSE, // suppress certain SSL errors
        CURLOPT_SSL_VERIFYPEER => FALSE, 
    ));
    curl_exec($ch);
    $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    return $url;
}

That’s all you have to do. This works even if multiple URL shorteners are used in sequence on the same link, and in the event of an error, you’ll just get the same link back that you started with.

This seems to be a bit more robust than some of the other solutions floating around the ‘net, since it doesn’t try to hack the response headers or body (which could change without notice), and it will recurse as many times as it needs to up to the value of the CURLOPT_MAXREDIRS setting.

Enjoy!

Blending CSS Gradients Like Photoshop

While on my day job over at Company 52, I encountered a textured background with a gradient overlay, using Photoshop’s overlay blending mode. I’m sure you’ve seen this effect before:

 

My first thought was to save the texture as a 4×4 PNG for tiling, and to save the gradient as a PNG with alpha transparency to overlay over the tiled pattern. This would certainly be lighter than saving a monolithic image, but this presented several problems:

  1. The size of the gradient PNG was still too large, especially when the gradient can be done in CSS
  2. The gradient overlay would be overlaid like Photoshop’s normal blending mode, not in the desired overlay mode, which would make the background look washed out.

CSS blending modes would solve this, but they aren’t here yet (you can do it with the HTML5 Canvas API, but that’s messy). Continue reading

IE innerHTML CSS bug

Today I encountered a bug that affects every version of Internet Explorer from IE6 all the way through IE9. Here’s what happens.

The Bug

  1. Set a Javascript string variable containing CSS styles (<style> or <link>) followed by some HTML content, such as a <p> tag (in my application this happened via JSONP).
  2. Add a <div> node to the DOM
  3. Insert the content from #1 into the .innerHTML property of the <div>
  4. The content gets inserted, devoid of CSS styles.
<!-- these styles are ignored when inserted via innerHTML -->
<style>
  p { color: blue; }
</style>
<p>Hello, how are you!</p>

The Solution

The simple solution was to put the other markup before the <style> or <link> tag. Bizarre.

<p>Hello, how are you!</p>
<!-- these styles are applied when inserted via innerHTML -->
<style>
 p { color: blue; }
</style>

Because IE needs something that affects the layout before it will apply CSS styles in an innerHTML property, these will not work as style triggers:

  • <!-- a comment -->
  • <p></p> (or any other empty block-level HTML tag)

Writing MySQL schema migrations: best practices

Building MySQL web applications with a team of developers will inevitably present the challenge of database schema changes. Any good web developer understands the importance of keeping all code under version control, but how many follow the same principle for the database?

What follows are some best practices that I have learned over the past several years from the school of hard knocks. Continue reading

Detecting mobile browsers via Javascript

Sometimes you want to do some sort of special processing in Javascript on mobile browsers, or vice versa. Here’s a quickie Javascript way to do that:

function is_mobile() {
	var agents = ['android', 'webos', 'iphone', 'ipad', 'blackberry'];
	for(i in agents) {
		if(navigator.userAgent.match('/'+agents[i]+'/i')) {
			return true;
		}
	}
	return false;
}

This version is fast and easy to extend to detect additional browsers by changing the agents array. Here’s a much more thorough version, modified from detectmobilebrowsers.com:

function is_mobile() {
	return /android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4));
}

Since this is a single gigantic regex, I suspect that it would run more slowly than the previous version, and is also more challenging to modify.

Instead of browser user-agent sniffing, you might want to consider using browser feature detection instead to tailor your application to the capabilities of the browser, rather than relying on the userAgent string (which can be customized in most browsers).

Enjoy!

Enabling a Dell Wireless 1370 network adapter (BCM4328) on Ubuntu Linux

After three recent virus infections on Windows XP and Windows 7 (including at least one rootkit infection), I turned to Ubuntu Linux as a safer operating system. Two of the PCs were blessed with Atheros-based wireless network adapters, which are well-supported on Linux. The other laptop, a Dell Inspiron 2200, is blessed with one of those infamous Broadcom chipsets.

Supposedly, the BCM4328 (rev 02) wireless chipset is supported on Linux, but as of Ubuntu Desktop 11.04 and Linux Mint 11, it doesn’t work reliably. So I turned to the old tried-and-true ndiswrapper to run the BCM4328 Windows driver under Linux. Continue reading

Master-Master MySQL Replication…that hurts less

If you have ever touched a MySQL slave, you know that they can and do frequently halt. While sync problems can be caused by many things—network outages, schema changes, etc—one of the most common problems in a dual-master setup is primary key collision.

Primary Key Collision

…happens when records are added on two different servers to the same table and get the same AUTO_INCREMENT value. Fortunately, there is a trivially easy way to prevent this from happening.

auto-increment-increment=N

Adding this to your my.cnf or my.ini file will make AUTO_INCREMENT increment by N rather than by 1. N is the number of replicated servers that are masters.

Combine this setting with…

auto-increment-offset=N

…to ensure that each replicated master uses unique AUTO_INCREMENT values. N should match the server-id setting.

With these two settings in place, your primary keys will never collide. Continue reading

Hello! I’m a self-taught freelance web developer, and this is my blog. Here, I document problems and solutions in PHP, MySQL, and Javascript that I encounter along the way. Enjoy!