Technology Solutions for Everyday Folks

LAMP to WAMP: Adventures in Server Migration

Server migrations are an inevitable task, but I found myself in a different than normal circumstance recently. A planned server stack retirement, combined with the server "owner's" technical capacity and expertise required a change in platform. Specifically, this shift meant moving from Linux to Windows.

WAMPserver, eh?

The server stack being retired could be classified as LAMP (Linux, Apache, MySQL, and PHP), though there is technically not an "M" component as the database/MySQL instance is hosted on a different stack unrelated to this change. A suggestion from the new server owner was to give WAMPserver a shot, and something to which I was certainly open.

In installing and giving WAMPserver a test, I wasn't terribly impressed overall. I didn't like the clunky interface and frequent CMD prompt popups when invoking commands/services. Additionally, the whole installation seemed to require installing every VC++ redistributable package on the planet. Ultimately to present a semi-helpful system tray menu and graphical interface to make basic changes in the server and service configurations. This came at the expense of many phantom CMD popups and an overall latency that just felt off to me.

I was able to get a test site up and running in a relatively short period of time (it took way longer to get all the prerequisites installed and wrap my head around how to use the WAMPserver interface). After an initial test, I also pulled a copy of the app project repo and configured it to present the application homepage as a real proof of concept that the project could be ported to Windows with a reasonable effort.

I was still stuck on the overall overhead, though. Ultimately I just need a fairly vanilla Windows instance of Apache and PHP, which are both easily obtainable and configurable. Especially since I have extensive experience with Apache configurations and VirtualHosts, stubbing out those "from scratch" is much less a lift than it could be for others. The absence of the "M" in L/WAMP was really the death knell in using WAMPserver for me, though. It definitely works, and I'm sure it has a place...especially in a full-stack setup that is overseen by someone with less familiarity.

To the question of managing Apache and PHP versions, though, those are both easily identifiable in the split disk system we set up (where almost everything is being installed on the D drive). No need to hunt through other directory trees to figure out what version(s) and installation dates.

My overall opinion of WAMPserver: YMMV. I probably wouldn't ever use it myself, but I can definitely see where having it would be helpful.

PHP

A straight up "install" (extraction) of PHP and its basic config is all that's necessary. With Apache, you have to use the Thread Safe version of PHP, which you can pull from the Windows PHP downloads page. In this particular project I pulled the VC15 x64 Thread Safe version of PHP 7.4.xx.

I extracted the PHP build to the D:\php7-4-xx\ path for simplicity and visibility. An important thing to configure, especially in using PHP with Apache, is to make sure that both the php.exe path and the PHP extension paths are added to the system PATH environment variable, as this will prevent trouble and confusion later on:

D:\php-7-4-xx
D:\php-7-4-xx\ext

PHP Configuration (php.ini)

Most/All of the basic PHP configuration happens in the php.ini file (located in the same directory as php.exe. You can copy one of the pre-stubbed-out versions (php.ini-development or php.ini-production) as a helpful starting point. The primary difference between the two is in the error_reporting variable, which you will want to review and modify according to your needs/tastes.

A couple of other key points and items to review in the php.ini file:

  • The extension_dir variable is picky and finicky. Having the system PATH environment variable set as noted above will address many module issues.
  • Make sure you review and enable the right combination of extensions. Referencing another/working host as an example is helpful here, and phpinfo() can be tremendously helpful in this configuration.
  • Because it prevents weird notices (and possible problems elsewhere), set the date.timezone variable accordingly. I almost always use 'America/Chicago' due to my own geographic location.
  • SMTP on Windows is non-existent. If you're sending email from this server you'll need an SMTP relay or MTA (or use a customizable application library like PHPMailer). Setting the SMTP, smtp_port, and sendmail_from values are necessary, similar to:
[mail function]
; For Win32 only.
; http://php.net/smtp
SMTP = your.smtp.mta
; http://php.net/smtp-port
smtp_port = 587

; For Win32 only.
; http://php.net/sendmail-from
sendmail_from = user@hostname.tld

You may need to modify other settings to taste, but everything is pretty self-contained in PHP configuration.

Apache

Apache installation and configuration requires more fiddling than PHP. Obtaining Apache from Apache Lounge downloads for Windows is the simplest. If extra modules are necessary, they have additional module pointers/downloads as well.

I "installed" the Apache build to the D:\Apache24\ path for simplicity and visibility. From there it's right into the httpd.conf file. I decided to make things more obvious for Future Me (or anyone else) that any modified or included conf files would be placed directly in the conf directory (aside httpd.conf).

In editing the base httpd.conf, some key points include:

  • SRVROOT definition (e.g. "D:/Apache24"), though note the comment below:
    • # NOTE: Where filenames are specified, you must use forward slashes
      # instead of backslashes (e.g., "c:/apache" instead of "c:\apache").
      # If a drive letter is omitted, the drive on which httpd.exe is located
      # will be used by default.  It is recommended that you always supply
      # an explicit drive letter in absolute paths to avoid confusion.
  • ServerSignature Off and ServerTokens Prod to obfuscate server details from prying eyes.
  • Listen 80 and Listen 443 (or the ports of your choosing/requirement).

Apache Modules

As usual, it's recommended to only load the modules required for your project or circumstance. In my case, this meant enabling (by uncommenting) the following:

  • LoadModule rewrite_module modules/mod_rewrite.so
  • LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
  • LoadModule ssl_module modules/mod_ssl.so

Adding PHP 7.4

Since PHP was installed separately, a link to the PHP Apache module has to be manually added, which I added to the end of the LoadModule list:

LoadModule php7_module "D:/php-7-4-xx/php7apache2_4.dll"
<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>
# configure the path to php.ini
PHPIniDir "D:/php-7-4-xx"

I also added index.php as a default directory request type:

#
# DirectoryIndex: sets the file that Apache will serve if a directory
# is requested.
#
<IfModule dir_module>
    DirectoryIndex index.html
    DirectoryIndex index.php
</IfModule>

Virtual Hosts

I made a copy of httpd-vhosts.conf and placed it in the root of the conf directory, but kept the Include commented out until I had a proper configuration ready:

# Virtual hosts
#Include conf/httpd-vhosts.conf

Apache as a Service

I installed Apache to run as a service with the httpd.exe -k install command. Much more detail about options in running and installing Apache on Windows is found in the Apache documentation.

I jumped over to Windows services to set the Apache2.4 service with a "Manual" startup type, especially useful during setup and testing. This kept the Apache service under my total control until things were ready to go live. At the same time, I "installed" the Apache Service Monitor which is a super helpful system tray application to quickly start/stop/restart Apache.

An Apache Test!

At this point it's feasible to actually start the Apache service and hit http://localhost:80 to see if you get the test page (or whatever content might be in the default htdocs directory). I skipped this particular step because I'd already set up a separate test/"Hello World" page in a VirtualHost config. Assuming Apache loads without error and you can view a basic page, you're ready to go with a "real" site!

VirtualHost Configuration

Editing the conf/httpd-vhosts.conf file I'd copied earlier, I made a basic host config to test:

<VirtualHost localhost:80>
    DocumentRoot "D:\path\to\test\web"
    ServerName localhost
    ErrorLog "logs/localhost-error.log"
    CustomLog "logs/localhost.log" common
</VirtualHost>

<Directory "D:\path\to\test\web">
    Options Indexes FollowSymLinks
    Require all granted
</Directory>

It's important to note the Directory directive in the above example. This is where you set the most basic security and access controls for the filesystem, including the ability to use and granularity of .htaccess files if necessary.

Restarting (or starting) Apache and hitting http://localhost:80 config should now present the default content stubbed out at the test site path. I just used a super basic index.html file:

<html>
  <head>
    <title>A Test?</title>
  </head>
  <body>
    <h1>A Test?</h1>
  </body>
</html>

Error Checking and Testing VirtualHost Configurations

These commands are useful under other circumstances as well, but running the folllowing commands will produce useful output to verify you configured things "appropriately" (in that they're not broken) for Apache:

  • httpd.exe -t to test the full Apache configuration. If all is good, you'll get a "Syntax OK" message.
  • httpd.exe -S to test and output details about the VirtualHost configurations. Super helpful to verify bindings and other details if something isn't behaving as expected. This will return several lines regarding the configuration.

Log Rotation and Management

It is possible to install the mod_log_rotate module for Apache on Windows, but the Apache installation comes with rotatelogs.exe to which log detail can be piped and managed without much issue. In the spirit of keeping with a "vanilla" mentality, I chose to go with piping to rotatelogs.exe as it reasonably handles the use case.

It's important to know that without any sort of log rotation mechanism Apache on Windows will just grow the log file indefinitely and infinitely. Using rotatelogs.exe does not address infinite disk growth (something separate needs to be employed to cull aged log files), but that is well outside the scope of this already lengthy post.

A simple way to add "daily" access log rotation (this can also be applied to the error log if/as necessary) is to add some detail in httpd-vhosts.conf:

<VirtualHost hostname.tld:80>
    DocumentRoot "D:\project\files\web"
    ServerName hostname.tld
    ServerAlias www.hostname.tld
    ErrorLog "logs/hostname/error.log"
    CustomLog "|bin/rotatelogs.exe -l D:/Apache24/logs/hostname/%Y-%m-%d.log 86400" common
</VirtualHost>

SSL Configuration

All of the SSL configuration details for this installation of Apache are outlined in detail in my last post about Certbot on Windows. SSL is one of the last steps to configure once the regular VirtualHosts are ready to go and tested.

One thing I like to do is "force" SSL on any enabled VirtualHost, which means adding a basic bit of Rewrite magic to the http/port 80 configuration:

<VirtualHost hostname.tld:80>
    DocumentRoot "D:\project\files\web"
    ServerName hostname.tld
    ServerAlias www.hostname.tld
    ErrorLog "logs/hostname/error.log"
    CustomLog "|bin/rotatelogs.exe -l D:/Apache24/logs/hostname/%Y-%m-%d.log 86400" common

    # Force SSL
    RewriteEngine On
    RewriteCond %{SERVER_PORT} 80
    RewriteCond %{REQUEST_URI} !^\/\.well-known\/.*$
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

Something to know is that it may be necessary in your non-SSL configuration to exclude the .well-known directory from rewrites to SSL as this will break certificate domain validation. This is especially true when using the Certbot --webroot plugin.

Finally, Porting a PHP Application!

I discovered in this process that any PHP application/website that is relatively well designed should Just Work when ported to a WAMP environment. I'd thought there might be some weird path things to change, but as it turned out there were no such changes necessary in the actual site/app code. Almost out of the box (well, the cloned repo on the proper branch to be specific), the application itself worked more or less perfectly!

A very welcome sight indeed. Also kind of unexpectedly disappointing and anticlimactic, but in a Good Way.

The things that needed modifications related to components now (in PHP 7.4) fully deprecated, and also a strange issue with setting session_name that has been broken for a long time and not previously caught or logged because it was silently recovering on the old host. All of this detail was available in the error.log and pretty straightforward to fix.

Before going live, I changed the production host's Apache2.4 service to have an "Automatic" start type so it gracefully recovers from any reboot (such as maintenance windows).

Day of Migration Considerations

In this project, and primarily thanks to the database server not changing, the actual migration between LAMP and WAMP host was handled via DNS. There was a period of under 10 minutes in which the actual transition took place. The only end user impact would have been on someone needing to initiate a new session (and thus login again) when they started pointing at the new host; however, due to the timing of the switch when no active sessions were in play, that situation was avoided.

A fresh set of SSL certs were created and installed within a minute of the DNS transition, and in log and analytics monitoring over the subsequent days it was verified that the migration was a success. The old host has since been fully decommissioned!

In all, it was a great learning experience and a relatively "simple" transition, and as always a good opportunity to cull some old mistakes! Never waste that opportunity to fix stuff Past You created, even if only in minor (or major) increments.