Forcing a PDF or DOC to Open in Browser Rather Than Downloading

If you click on a PDF or DOC link on a website, your Browser will either:

  1. Download the PDF or DOC (with or without prompting a Save-As).
  2. Open the PDF or DOC via Adobe Reader or Microsoft Word – in the Browser’s window/tab.

Most visitors come to just read the docs, preferably and automatically in 1 step. They probably don’t really want to download those docs, and then go through the process of manually opening the downloaded file in Adobe or Office.

Whether the Browser opens or downloads the file depends on the:

To force the inline viewing of PDF files, edit the website’s HTTP and HTTPS VirtualHost files, and inside the VirtualHost block add in configuration:

<LocationMatch "\.(?i:pdf)$">
    ForceType application/pdf
    Header set Content-Disposition inline

As an alternative, to rather force downloads (as opposed to inline viewing/opening), use:

<LocationMatch "\.(?i:pdf)$">
    ForceType application/octet-stream
    Header set Content-Disposition attachment

Save file(s). Restart Apache for changes to take effect.

Result – Automatic (no download or prompt) in-browser viewing of PDF (IE 9):

Alternative – Downloading of PDF (IE 9):

You will need to clear your Browser’s cache (files, cookies, and history – yes, everything) and close it, every time you test this. Otherwise, you’ll keep getting the previous behavior. If the old result persists because the Browser’s PDF or Office Word plugin is caching the data itself, try renaming the file.

Normally, Apache and WAMP servers (such as WampDeveloper Pro) have a MIME-type-to-file-extension association file (Config\Apache\mime.types) that sets the correct “Content-type” Header for PDF and DOC files. But sometimes, depending on the website’s configuration and where and how the PDF files are located and/or generated, the above Header configuration is required.

It is also very important to use “LocationMatch” instead of “FilesMatch” because:

  • Your PDF and other document files might be generated or transferred by PHP – which will not be detectable through Apache’s Files directives.
  • Of the way Apache applies and merges Directory, Files, and Location sections – with Location sections taking effect with precedence over the Files sections (that might be causing your current issue).

Note: Apache’s “Header” directive requires mod_headers, which is usually loaded by WAMP, otherwise…

<IfModule !mod_headers.c>
    LoadModule headers_module modules/

Transferring Document Files Through PHP

If you are transferring the document files through PHP, chances are the unwanted behavior is happening due to this code…

header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($fullpath));
header('Content-Disposition: attachment; filename="' . $file . '"');

In this case, you can either search your code-base for some partials of the above strings, and comment out or update the lines with the proper Content-Type and Content-Disposition values, or use the provided Apache header configuration to post-process and replace the PHP generated headers.

Running IIS and Apache Together On the Same Server at the Same Time

It is possible to run both IIS and Apache on the same Windows machine, both using port 80 (http) and 443 (https), without any start-up or conflict issues.

To do this, you simply configure IIS and Apache to use different interfaces (i.e., IP addresses) with: one web-server binding on the Public IP address, and the other web-server binding on (local host). Depending on your situation and needs, you can also assign the LAN IP to either IIS or Apache (…just ask yourself which web-server do you want reachable on the local/internal network?).

This way you’ll have both web-servers, and their hosted websites, using standard ports (with no need to use non standard URLs), just on different IPs.

With this method (for example), you could keep your existing IIS websites in production, while at the same time developing on and/or migrating to Apache (which is accessible on the local host). Later, you can move Apache to production by re-binding it.

1. Rebind all IIS websites (including “Default Web Site”) from “*” and “” (“″ means all IPs the system has) to the Public IP address… Select each website one by one, and in the Action pane, click Bindings.

2. Bind Apache from to per these simple instructions…
Binding Apache to a Specific Or Secondary IP Address

Note that when you install a WAMP server such as WampDeveloper Pro, it will stop the IIS Service and change its “Startup Type” from “Automatic” to “Disabled”…

To minimize production downtime, after WAMP installation –

  1. Stop Apache.
  2. Go into Services (run services.msc), select the IIS Service (W3SVC / “World Wide Web Publishing Service”), change its startup type back to “Auto”, and start it.
  3. Rebind all IIS websites to the public IP address, and restart IIS.
  4. Rebind Apache to per the above instructions, and start it.

Generating and Installing Wildcard and Multi-Domain SSL Certificates

Generate a CSR (Cert Signing Request) For a Wildcard Domain

Normally, to generate a certificate for a wildcard domain such as *, all you have to do (when generating the CSR) is specify in the “Common Name” field:

The problem is that that:

  1. This will only wildcard 1 sub-domain level (i.e., it will not work for,
  2. And it will not cover the root domain (i.e., “”,

To cover additional domains and wildcards, you have to use openssl’s SAN (subjectAltName) extension

1. Edit file openssl.cnf (open via Notepad) –
File C:\WampDeveloper\Config\Apache\openssl.cnf

2. Uncomment (remove starting ‘#’) line:
# req_extensions = v3_req # The extensions to add to a certificate request

req_extensions = v3_req # The extensions to add to a certificate request

3. Update the “[ v3_req ]” section with line:
subjectAltName = @alt_names

[ v3_req ]

# Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

4. Create file named “alt-names.txt” and place the entire list of all domains and wildcards into it (including the previously entered “Common Name”):

[ alt_names ]
DNS.1 =
DNS.2 =
DNS.3 = *
DNS.4 = *.*

Note that entry “*.*” wildcards on multiple level sub-domains. This entry might, or might not work, depending on how different Browsers decide to handle this and if the CA (Certificate Authority) allows this.

5. Follow the exact instruction on generating a CSR, except make sure to add the “alt-names.txt” file into the CSR generation command…

openssl genrsa -out example_com.key 2048
openssl req -new -sha256 -key example_com.key -out example_com.csr -config C:\WampDeveloper\Config\Apache\openssl.cnf -extfile C:\WampDeveloper\Config\Apache\alt-names.txt

The first line generates your private key. The next line generates the CSR, using the additional entries from the alt-names.txt file. At this point you can either input the contents of CSR file into the CA’s certificate purchasing process, or self-sign the cert…

Self-Signing a CSR (Certificate Signing Request) For a Wildcard Domain

If you are going to self-sign this certificate, you will need to tell the CA configuration to allow and use the SAN extension, by uncommenting in file openssl.cnf, line:
# copy_extensions = copy

[ CA_default ]
# Extension copying option: use with caution.
copy_extensions = copy

Then create the self-signed wildcard certificate the exact same way as in all other cases:

openssl x509 -req -sha256 -days 365 -in example_com.csr -signkey example_com.key -out example_com.crt

Installing Wildcard and Multi-Domain Certificates

There is no difference between how Apache (nor any other web-server such as IIS, Nginx, Tomcat) treats normal and wildcard certs.

You would install the certificates the regular way, with a separate update to each website’s SSL VirtualHost file, on the location/path to the: cert, bundle file (if exists), and private key (all of which can point to the same locations for each website, or can be duplicated into each websites’ certs\ folder)…

For example see Installing Comodo PositiveSSL Certificate Bundled with Root and Intermediate CA Certificates on Apache.

Note that if you self-signed the certificate:

  1. There will be no bundle file (don’t use “SSLCertificateChainFile” directive).
  2. And if you want your local OS and Browser to actually accept and pass this certificate (without blocking website access as “untrusted”), you are going to have to install it into Windows Trusted Root Certification Authorities store. *Some Browsers do not use this store and have their own “trust exception” process.

Running EXE and BATCH Files As CGI Scripts In Apache Under Windows

If you need to run an executable file as part of the request made to your server, get its output, and pass that output back to the client, there are 3 things that you have to be aware of:

  1. EXE files are typically Windows command-line (or Desktop) binary files that where not built/compiled to be executed through CGI.
  2. Everything that is ran through CGI, has to conform to the CGI 1.1 Specification.
  3. The CGI specification only requires that the first two output lines are: a Header line (e.g., “Content-type: text/html”) followed by an empty line.

To execute regular EXE files as CGI scripts, read on…

Mistake #1. “AddHandler cgi-script .exe”

The most common mistake is to treat all EXE files as CGI compliant script files by using configuration:

AddHandler cgi-script .exe

This is not going to work because the EXE file:

  • Is not a CGI compliant binary.
  • Nor is it a script file with a readable “shebang” line (which is the first line of file that specifies what interpreter is used to run this “script”).

Mistake #2. “#!C:\windows\system32\cmd.exe /c”

Another common mistake is to wrap the EXE file into a BATCH (BAT) file, and then using the “shebang” line to tell Apache to execute the script via cmd.exe.

This will not work, and will either produce an “500 / Internal Server Error” or a blank page, as when that BATCH script is ran it will output an error as its first line is read (i.e., the regular “#!…” shebang line is not a valid BATCH ignore/comment line).

The Right Way To Execute Regular EXE and BAT Files as CGI Scripts

1. Wrap the EXE in a BATCH (.bat) file, with the proper cgi-compliant header and header-body separator.

@echo off
echo Content-Type: text/plain

“dir” is a standard command-line binary that outputs the current directory’s file + folder names. Substitute your binary in.

2. Do not use a “shebang” line, by directly telling Apache to execute it via cmd.exe

Update Apache configuration for the directory either in the main configuration, the website’s VirtualHost file, or the .htaccess file:

ScriptInterpreterSource Registry-Strict
AddHandler cgi-script .bat
Options +ExecCGI +FollowSymlinks
<IfModule mod_ssl.c>
    SSLOptions +StdEnvVars

WampDeveloper Pro already has this configuration for /cgi-bin.

You can also set a cgi error log with line: ScriptLog /path/to/logs/cgilog.txt

Create and set Registry Key/Value to shell execute .bat files via cmd.exe:

Key: HKEY_CLASSES_ROOT\.bat\Shell\ExecCGI\Command
Value name: (default)
Value type: REG_SZ
Value data: "C:\windows\system32\cmd.exe /c %s %s"

Run “regedit” to open the Registry editor. You will need to create key “Shell\ExecCGI\Command” as it’s unlikely to already exist.

3. And if elevated privileges are needed to run this EXE file, switch Apache to use a different account in its Service Properties / Log On field.


To output HTML, you’ll have to set the proper Content-type header, and escape special BATCH characters…

@echo off
echo Content-Type: text/html
echo ^<HTML^>
echo ^<HEAD^>
echo ^<TITLE^>Hello, world!^</TITLE^>
echo ^</HEAD^>
echo ^<BODY^>
echo ^<H1^>Hello, world!^</H1^>
echo ^<p^>Hello, world!^</p^>
echo ^</BODY^>
echo ^</HTML^>

BATCH Environment

The environment of the CGI has variables containing passed in parameters (QUERY_STRING) and other info


@echo off
echo Content-Type: text/plain


HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
HTTP_ACCEPT_ENCODING=gzip, deflate, sdch
PATH=...removed for brevity...
SERVER_SIGNATURE=<address>Apache/2.4.10 (Win32) OpenSSL/1.0.1j PHP/5.6.4 Server at Port 80</address>
SERVER_SOFTWARE=Apache/2.4.10 (Win32) OpenSSL/1.0.1j PHP/5.6.4

Redirect All HTTP Traffic To HTTPS (Port 443)

Redirecting website HTTP (port 80) traffic to HTTPS (port 443) is very easy under Apache.

  1. Open the website’s HTTP VirtualHost file.
  2. Inside the VirtualHost block, near the very end, add line (with your website’s primary domain name substituted in):
    Redirect /
  3. Also make sure the HTTPS VirtualHost file does not have a similar line in it that redirects back to HTTP, or your Browser will go into a loop.
  4. Save VirtualHost files. Restart Apache.
    1. For this to work correctly, without any Browser warnings, your website will need to have the proper SSL Certificates installed. Most Browsers will not allow you into a website that is using self-signed certificates (nor mismatched “Common Name” / domain name certs).

Installing WAMP on Windows Server 2012

Windows Server 2012 has several different installation types (the minimal “Server Core”, “Minimal Server Interface”, “Server with a GUI”, “Full”), and different options/features to select from.

Some features are required to run WAMP, while others have to be removed.

To be able to install and run Apache, MySQL, and PHP (i.e., WAMP) on Windows Server 2012, these are the minimal options/roles/features:

* .NET 3.5 is not absolutely required, as most .NET 3.5 applications can be ran under .NET 4.5 (i.e., the Windows Server 2012 default .NET verison).

If you have installed “Server Core”, you will need to switch/convert it to “Server with a GUI” (which can be done with a number of commands).

It is best to not have additional Roles installed, such as: “Web Server (IIS)”. As these Roles and Features will have Services and processes that take port 80 and 443, which Apache needs to run (check Disable Services that Interfere with Apache).

You will also need to install the “C++ Re-distributable for Visual Studio 2012″ which contains the DLLs that Apache and PHP are compiled against (check Required Microsoft VC++ Redistributable Packages).

WampDeveloper Pro’s Web-app Auto-Installer Internals

If you are interested in creating your own web-app packages (such as a custom package of WordPress with various themes and plugins integrated and enabled) that are then “one-click” easily (for deployment) and repeatably (for testing) installable to any website and URL, WampDeveloper Pro provides its own web-app packaging format and installation mechanism…

The web-app installer reads and executes commands (that it understands) from an XML file – performing the task of placing, importing, editing, and manipulating web-app assets (files, configuration, database entries). This process creates an “installation” that is [ideally] identical and indistinguishable from a regular install.

The principle difference between WampDeveloper’s web-app installer and the web-app’s native setup/installation process is:

Native setup/installation – runs through the web-app’s PHP code, which forms the database, populates that database, and updates the configuration file.

WampDeveloper’s web-app installer – bypasses the above native process completely by placing the web-app’s files, importing the already formed database, and using the install-to path and URL, updating the configuration and database entries to reflect the new location.

To create a web-app package:

Web-app Assets

Create the needed assets for the installer to start with…

1. Manually install a web-application using these default values:

username: admin
password: passadmin
DB name: webapp
DB user: webapp
DB host:

Technically you can use any values you’d like, but the above will be considered as standard. These values will later be changed by the auto-installer using the selected website, URL, and the generated DB name + user and Admin password.

When installing the web-app, try not to go pass the finish of the installation process – by accessing the home page and/or performing a login. If you do so, the web-app will likely create sessions, caches, temporary files, and database entries, that might need to then be cleaned up.

2. Perform a clean up by deleting any created:

A. Sessions - e.g., truncate table "sessions"
B. Caches - e.g., truncate tables "cache_*"
C. Watchdog (log) - e.g., truncate table "watchdog"
D. Any cache and temps files - e.g.,  files in web-app's "temp" sub-folder

This step is not necessary, but it is considered good to not leave any residual fingerprints and data.

3. Export the web-app’s database into an SQL file, and zip the file as: \database\

4. Select all the web-app’s files (but not the containing folder), and zip the selection as: \files\

5. Copy the web-app’s configuration file(s) into: \configs

Package Format (Example)

All the web-app packages are stored in folder:

If you take a look into folder \WampDeveloper\WebApps\wordpress-\ , next to the base assets, there are three XML files that define and control the installation of WordPress.

All other files/folder within are just things referenced and used by tasks.xml (WampDeveloper only knows about the 3 XML files).

1) tasks.xml: which defines what to do to install the package (and move it to another URL, delete it, etc).

2) parameters.xml: which defines the variables and defaults (such as “db_host” is to be “” / localhost by default), that will be used later in tasks.xml.

3) profile.xml: which just defines the name, version, and some other “info” data about the web-app and package.

Installation Tasks

The way it works is tasks.xml:

a) Imports \database\wordpress.sql(.zip) into MySQL.
b) Imports \files\files(.zip) into the install-to directory.
c) Imports \configs\wp-config.php into the install-to directory (or where WordPress expects to find it).
d) Performs SQL queries to create the proper database name, database user, and update the database with the proper values for the new URL, path, etc.
e) Performs file edits to update wp-config.php with the proper values for DB name + user + host, keys and salts, etc.
f) Runs a script (scripts\setPassword.php) that uses WP’s API (functions) and a passed-in command-line parameter to set the admin password.

Creating Web-app Package

If you have the time to study the format/syntax of the above XML files, you can create your own packages. The difficulty of it is that none of this is fully documented for the moment, as it’s all still evolving. And if it’s not done correctly, it’s just going to fail without providing any type of useful error message.

If you wanted to create your own WordPress package with your additional configuration, content, themes, and plugins included, just install WordPress (and additions) into:\blog. Then, as shown above: export and zip the database, copy the wp-config.php file, zip up the files, and replace those assets within a duplicate of the already provided WP package (i.e., in a copy of the provided webapp-version package – with the folder name updated for version and release number). Afterwards make some minor edits to profile.xml and tasks.xml to reflect the new version and possibly any new changes.


Create auto-installer for the latest version of web-apps such as Wordress.
Create auto-installer for any PHP Framework such as CakePHP, CodeIgniter, Zend.
Create auto-installer for any Frontend HTML + CSS Framework / Template such as Bootstrap.
Create auto-installer for any Javascript MVC Framework or Library such as AngularJS.
Create auto-installer for executable (binary) applications such as Node.JS.
Create auto-installer for an entire website (including its databases).
Create auto-installer for full Windows applications, including IDEs (editors).
Create auto-installer for Mobile Frameworks such as Ionic.