Category Archives: Apache 2

Secure and extend you Philips Hue bridge with a reverse proxy

Philips Hue bridge

Recently I bought a Hue bridge with two bulbs, it was a specific “cheap” pack around 50€, the bridge itself usually costs that price, so I was quite interested. My idea was just to play with those bulbs and do funny things such as blinking when I get new mail, etc … After messing with it for a while using the Philips HUE app on Android, I wanted to do more so I checked the API. It’s quite well made and allows you to do a lot on your own, though, I had in mind to hide my bridge in my own local network, for security purpose and also to add more functionalities to it, let’s see how to make a hue bridge reverse proxy !


To do that, you need:

  • a router, your own home box is enough
  • a raspberry PI or any computer with Apache and PHP
  • (optional) a USB to ethernet adapter, I bought this cheap one
  • some time to configure it all !

Configure the bridge

Follow the official instructions to install your bridge, you have to connect it to your router, it needs an IP within your local network so we can reach it with an other computer.

Once it’s all set and that it got an IP, open up your web browser and go to the following URL:, obviously set the IP address to the correct one.

Create a new user following the steps on the API documentation. Keep the generated hash around, it’s important to control your bridge.

Stay in your web browser in the debug and do a GET call to http://<ip-address.of.the.bridge>/api/<username>/config, it will look like that:

Get the bridge information

Get the bridge information

Copy the mac field and save it somewhere, it’s important.

Finally, we will configure the bridge to stop DHCP and also to take the IP we want it to get out of the local range.

Still in your browser, do a PUT request to http://<ip-address.of.the.bridge>/api/<username>/config with the following content:

{"ipaddress":"", "dhcp":false, "netmask": "", "gateway": "" }
Do a PUT request to http://<ip-address.of.the.bridge>/api/<username>/config

Once you run it, you should lose the control to the bridge ! No worries, we’ll get it back.

Network adapter

If you bought the network adapter I suggested, it should look like that:

Network adapter in its blister

Network adapter in its blister

SSH to your raspberry pi, and check the network configuration:

 # ifconfig -a
 eth0      Link encap:Ethernet  HWaddr XX:XX:XX:XX:XX:XX
           inet adr:  Bcast:  Masque:
           RX packets:1575384 errors:0 dropped:62 overruns:0 frame:0
           TX packets:810579 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 lg file transmission:1000
           RX bytes:271762165 (259.1 MiB)  TX bytes:189260556 (180.4 MiB)
 lo        Link encap:Boucle locale
           inet adr:  Masque:
           UP LOOPBACK RUNNING  MTU:65536  Metric:1
           RX packets:4781492 errors:0 dropped:0 overruns:0 frame:0
           TX packets:4781492 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 lg file transmission:1
           RX bytes:402951806 (384.2 MiB)  TX bytes:402951806 (384.2 MiB)
ifconfig -a before plugging the adapter

Now plug the network adapter USB side to your PI, and connect the ethernet cable to the ethernet adapter.

Network adapter plugged

Network adapter plugged

We’ll check the adapter is working by doing the same command, but now we should see eth1 !

 # ifconfig -a
 eth0      Link encap:Ethernet  HWaddr XX:XX:XX:XX:XX:XX
           inet adr:  Bcast:  Masque:
           RX packets:1576244 errors:0 dropped:62 overruns:0 frame:0
           TX packets:811252 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 lg file transmission:1000
           RX bytes:271834756 (259.2 MiB)  TX bytes:189382050 (180.6 MiB)
 lo        Link encap:Boucle locale
           inet adr:  Masque:
           UP LOOPBACK RUNNING  MTU:65536  Metric:1
           RX packets:4781492 errors:0 dropped:0 overruns:0 frame:0
           TX packets:4781492 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 lg file transmission:1
           RX bytes:402951806 (384.2 MiB)  TX bytes:402951806 (384.2 MiB)

 eth1      Link encap:Ethernet  HWaddr XX:XX:XX:XX:XX:XX
           BROADCAST MULTICAST  MTU:1500  Metric:1
           RX packets:0 errors:0 dropped:0 overruns:0 frame:0
           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 lg file transmission:1000
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
ifconfig -a after plugging the adapter

Perfect, now let’s configure the network, edit the file /etc/network/interfaces by adding the following:

 auto eth1
 iface eth1 inet static
         netmask 24

Bring up eth1 end ping your bridge:

#  ifup eth1
 # ping
 PING ( 56(84) bytes of data.
 64 bytes from icmp_seq=1 ttl=64 time=1.86 ms
 64 bytes from icmp_seq=2 ttl=64 time=1.45 ms
 --- ping statistics ---
 2 packets transmitted, 2 received, 0% packet loss, time 1001ms
 rtt min/avg/max/mdev = 1.454/1.661/1.868/0.207 ms
Bring up eth1 and ping your bridge !

If it all works, you can move on to the HTTPS part, else, try using some networking tools such as tcpdump to determine what goes wrong.


New release of the Hue bridge uses HTTPS, of course it can’t rely on a real domain name since it’s connecting on the IP of the bridge and it can be different, after doing some reverse engineering it I found out how it works. Actually every bridge as an ID. Now that the link between your PI and the bridge is UP you can get your ID easily and prepare to create your own HTTPS certificate.

# openssl s_client -showcerts -connect </dev/null
Server certificate
subject=/C=NL/O=Philips Hue/CN=00xxxxxxxx
issuer=/C=NL/O=Philips Hue/CN=00xxxxxxxx
Check what's the CN of your bridge

So there you go you have your CN which is actually what’s necessary to create your own HTTPS certificate. If you want to change your ID, you can, you’ll have to change it also in the PHP below.

First create a directory to store your key, certificate.

# mkdir -p /etc/ssl/hue
Create the /etc/ssl/hue directory

Now the key and the certificate

# openssl req -newkey rsa:4096 -nodes -keyout key_hue.pem -x509 -days 3650 -out certificate_hue.pem
Create your self signed certificate

The only parameter that matters here is the CN, put a correct bridge ID, in my case I just changed a few values.

Finally just concat both files to create a pem.

# cat certificate_hue.pem key_hue.pem > pem_hue.pem
Create the full certificate

A word of advice, as of now (version 1806051111 of the bridge), the hue app will use HTTPS to connect to the bridge, the first time you validate the connection it will stick the certificate. If you ever change the certificate, you’ll have to remove the credentials in your Android/iOS (i.e clear all data of the app) and press on the button again.

Second word of advice, if you test a lot, be careful to keep clean your whitelist user, it gets messy very fast ! You can delete some doing a DELETE request on /api/userYouControl/config/whitelist/userYouWishTodelete

Reverse proxy

Do you remember the MAC address of your bridge ? If, as I previously said you did, then we will use it pretty soon.

Stay on your PI and open up again /etc/network/interfaces, we will change the mac address of eth0 so if it will be seen as a HUE bridge for the mobile apps. You can use the mac address of the real bridge and alter it so it’s different, I suggest you use this website to make it sure it’s still seen as “Philips Lighting BV“.

auto eth0
iface eth0 inet static
        netmask 24
        # faking MAC address to Philips HUE style
        hwaddress ether 00:17:88:78:45:12
Change the mac address

Now on your home box, set the IP of your PI as a static one for this tutorial it will be

It’s time to install haproxy, apache and php, I won’t describe this here, do as you wish, we’ll just go through what’s really important.

There goes a valid haproxy configuration:

# Faking Hue Bridge requires HTTPS now

frontend f_http_hue
        mode http
        use_backend b_http_hue

frontend f_https_hue
        mode http
        bind ssl crt /etc/ssl/hue/pem_hue.pem
        use_backend b_http_hue

backend b_http_hue
        mode http
        server bridge

Install mod_proxy and mod_http_proxy, create a new virtualhost /etc/apache2/sites-available/hue.conf this way:

    DocumentRoot /var/www
    ServerName xxxxxxx
    CustomLog /var/log/apache2/hue_access.log combined env=!forwarded
    CustomLog /var/log/apache2/hue_access.log proxy env=forwarded
    ErrorLog /var/log/apache2/hue_error.log

    RewriteEngine On
    RewriteCond %{REQUEST_URI}  ^$ [OR]
    RewriteCond %{REQUEST_URI}  ^/$ [OR]
    RewriteCond %{REQUEST_URI}  \.png$ [OR]
    RewriteCond %{REQUEST_URI}  \.xml$ [OR]
    RewriteCond %{REQUEST_URI}  ^/debug/clip.html
    RewriteRule (.*)    $1    [P,L]

    RewriteRule ^(/api.*)$ /index.php?q=$1 [L,QSA]

Activate it and edit /var/www/index.php:

* Activate proxy_module and proxy_http_module


$mac_address = [ '<mac address of the real bridge>',  '<your fake Philips HUE mac address>' ];
$ip_hue = [ '<ip of the real bridge>', '<ip of your PI>' ];
$bridge_id = [ '<the real bridge id>', '<faked id bridge>' ];
$gateway_hue = [ '<ip of your PI>', '<gateway of your local network>' ];
$url = ''.$_SERVER['REQUEST_URI'];
$useDB = false;

$options =[ 
    'http' => [
        'header'  => "Accept-Encoding: gzip, deflate\r\nAccept-language: en-US,en;q=0.8\r\nUser-Agent: ".$_SERVER["HTTP_USER_AGENT"]."\r\n",
        'method'  => $_SERVER["REQUEST_METHOD"],

    $options['http']['header'] .= "Content-type: application/x-www-form-urlencoded\r\n";
    $options['http']['content'] = file_get_contents("php://input");

$context  = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) {  }

// faking HUE bridge EDIT: 13/08, it needs to be faked all the time now
$result = str_replace($mac_address[0], $mac_address[1], $result);
$result = str_replace($ip_hue[0], $ip_hue[1], $result);
$result = str_replace($gateway_hue[0], $gateway_hue[1], $result);
$result = str_replace($bridge_id[0], $bridge_id[1], $result);

echo $result;
$size = ob_get_length();
header("Content-Length: {$size}");
header("Connection: close");

/** DB part **/

    $_user = '';
    $_password = '';
    $_database = '';
    $_host = '';

    try {
	$_db_link = new PDO('mysql:host='.$_host.';dbname='.$_database.';charset=utf8', $_user, $_password);
    } catch (Exception $e) {
        die('Erreur : ' . $e->getMessage());

    $stmt = $_db_link->prepare("INSERT INTO hue_log (ip, method, url, content) VALUES (:ip, :method, :url, :content)");
    $stmt->bindParam(':ip', hash('sha256', $_SERVER['REMOTE_ADDR']));
    $stmt->bindParam(':method', $_SERVER['REQUEST_METHOD']);
    $stmt->bindParam(':url', $_SERVER['REQUEST_URI']);
    $stmt->bindParam(':content', $result);

Now restart Apache and open your browser on, it should load ! Check the logs of Apache, you should also see some requests.

Philips’ upgrades

I didn’t mention it, because I hadn’t figure it yet, but all the Philips’ Upgrades won’t be done anymore since the bridge has no internet access. I found out one way to do it, it’s tricky but anyone can do it.

I use the Hue App on my mobile to control lights, it tells you when some upgrades are required, it’s how I know I should run them. When it happens, I simply forward packets from my bridge to my PI and force the update thought the API, let’s do it.

Allow packet forwarding and forward the bridge to the PI:

 echo 1 > /proc/sys/net/ipv4/ip_forward
 iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
 iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
 iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
Allow kernel forwarding and forward bridge to the PI

You should notice that the third light is now lit, it reaches internet, no worries we open it up only temporarily.

Connect to the API and run the following commands:

         "portalservices": true
Do a PUT request to http://<ip-address.of.the.bridge>/api/<username>/config
{"swupdate": {"checkforupdate":true}}
Do a PUT request to http://<ip-address.of.the.bridge>/api/<username>/config
         "swupdate": {
                     "updatestate": 3
Do a PUT request to http://<ip-address.of.the.bridge>/api/<username>/config

It should download the update, restart your bridge, launch the update from your mobile app, sometimes it’s quite long ! To update three bulbs it took me around 1 hour, it depends on the update.

Once everything is done, cut the bridge from the internet:

 iptables -F nat
 iptables -F FORWARD
 echo 0 > /proc/sys/net/ipv4/ip_forward
Clean up the firewall and shut down forwarding

Reboot it once again so remaining connections will be stopped. Only two lights should remain.

You might check that your bridge is really off the internet by doing a GET request to http://<ip-address.of.the.bridge>/api/<username>/config, you should have that:

     "portalstate": {
         "signedon": false,
         "incoming": false,
         "outgoing": false,
         "communication": "disconnected"
     "internetservices": {
         "internet": "disconnected",
         "remoteaccess": "disconnected",
         "time": "disconnected",
         "swupdate": "disconnected"
Content of the GET to http://<ip-address.of.the.bridge>/api/<username>/config

Also, it’s possible you do a tcpdump to check to what your bridge tries to contact, it might amuse you.

Sources for the upgrades:


Your HUE bridge is hidden behind your raspberry PI, it’s secure moreover you can also edit the PHP to add new functionality !

Cache and compress your favicon !

Favicon ?!

Any “good” website comes with a favicon, that little picture near the URL bar in your web browser. I’ve noticed recently that for mine, it couldn’t be cached by web browsers and that neither was it compressed as you may notice on that

Screenshot of failed favicon caching

So let’s solve that quickly !

Set the MIME type

First of all, I have no clue why, but Apache2 does not recognize .ico correctly, so you have to set the MIME type by yourself, let’s do it:

AddType image/x-icon .ico

If you do not specify that binding, the following steps won’t work !


Now let’s activate the expires module if it’s not already done:

a2enmod  expires
Activate expires mod

And let’s add our caching rule:

ExpiresByType image/x-icon "access plus 1 year"

I set the caching time to one year since I don’t change frequently my favicon, it’s up to you.


The deflate module allows Apache2 to compress stuff, here we’ll first activate it:

a2enmod deflate
Activate deflate mod

We need to configure it now:

AddOutputFilterByType DEFLATE image/x-icon

Last but not least, restart Apache2:

service apache2 restart
Restart Apache2


Let’s run the test once again at !

Best grade at webpagetest for compressing images

And the favicon is no more within the “not compressed” nor “not cached” section:

Favicon cached and compressed !


I do agree that it’s just the favicon who cares, but you can use that configuration for other kind of assets, such as pictures, scripts, stylesheets, …

Save RewriteCond’s backreferences to use in RewriteRule

Using Apache’s mod rewrite, it’s possible to redirect URL and stuff. One cool thing is to use backreferences and use them as environment variables. For example when you have several RewriteRule sharing some common part(s) but are different … the bad example under will, I hope, be clearer.

	# in .htaccess or vhost
	RewriteEngine On
	# define a RewriteCond with two capturing part
	RewriteCond %{HTTP_HOST} ^(.+?).(test|prod).com$ [NC]
	# there we define the value of the subdomain (i.e %1) and the host (i.e %2 which can be test or prod)
	RewriteRule .? - [E=SUBDOMAIN:%1,E=HOST:%2]
	# we apply the RewriteRule using our variables
	RewriteRule ^js/(.+?).js$ %{ENV:SUBDOMAIN}.php?js=$1&host=%{ENV:HOST} [L]
	RewriteRule ^css/(.+?)/(.+?).css$ %{ENV:SUBDOMAIN}.php?folder=$1&css=$2 [L]
	# other useless example of how to do so
	RewriteCond %{HTTP_USER_AGENT} ^(Mozilla.+) [NC]
	RewriteRule .? - [E=HASMOZILLA:%1]	
	RewriteCond %{REMOTE_ADDR} =
	RewriteRule ^here$ %{ENV:SUBDOMAIN}.php?who=me [L]

Basically the part to remember is this trick wich allows to save environment variables:

        RewriteRule .? - [E=VAR1_NAME:VALUE,E=VAR2_NAME:VALUE]

Do not put any space between values, this example will give an internal error (space after the comma):

        RewriteRule .? - [E=VAR1_NAME:VALUE, E=VAR2_NAME:VALUE]

And if you have a RewriteCond above with capturing part(s), you can use backreference (%1 and so on), just like in the first example.

Finally, you can use the environment variable in your RewriteRule like so:


Voilà !

An .htpasswd with an IP bypass

Hello everyone,

today is about some Apache “trick” to restrict access on your website. It’s using .htpasswd as you may know. A common problem with .htpasswd is to restrict access with a password, the thing is that you don’t want to type a password all the time, who remembers it? We are all so lazy … me first 🙂
So here goes a configuration which gives you the possiblity to bypass the password for a set of allowed IPs, domains, etc …

This configuration can go into an .htaccess or a virtualhost:

<Location />
        AuthUserFile /path/to/the/.htpasswd
        AuthName "Restricted area"
        AuthType Basic
        require valid-user
        Satisfy Any
        Order allow,deny
        Allow from

How does it work?
If your IP is then you won’t be asked for the password, but if you have a different IP, then you have to remember the password !
Voilà 🙂

How to restrict php directives using php_admin_value and php_admin_flag

Let’s say you are dealing with a shared Apache server running PHP as an Apache module, you have several websites running, and you don’t want to let some php directives being changed. You allow the modifications of the directives such as  display_errors but you don’t want memory_limit to be changed by any of the users (let’s say 128M) also, you don’t want to say that your server runs PHP (i.e expose_php off).

It’s possible to change the values of the php.ini directives in three differents ways:

  • apache configuration (i.e virtualhost(s), apache2.conf, etc …)
  • .htaccess (if AllowOverride Options or AllowOverride All)
  • ini_set function

There are two ways to force the values of the php directives and make them unchangeable from .htaccess or with ini_set function.

php_admin_value <setting> <value>

This one forces a directive to a certain value (non boolean), for example:

php_admin_value memory_limit 128M

We force memory_limit to 128M.

php_admin_flag <setting> <on|off>

This one does the same but for a boolean value, for example:

php_admin_flag expose_php off

There the directive expose_php will be unset.

To do this for ALL the websites on your server, you can edit your /etc/apache2/apache2.conf (may change if you have httpd) and put this content at the end:

Include php_restrictions

Create the file  /etc/apache2/php_restrictions, and add this content:

php_admin_value memory_limit 200M
php_admin_flag expose_php off

You can add as many rules as you wish, then reload your webserver like that (may change if you have httpd):

/etc/init.d/apache2 reload

To do this for only ONE specific website (i.e virtualhost), just set the rules inside the virtualhost like that:

    DocumentRoot /var/www/test
    # now the rules
    php_admin_value memory_limit 128M
    php_admin_flag expose_php off

This will apply only to the website.

Then you need to reload the webserver too.

Voilà, you have restricted some directives and some others are still changeable.

Though, some directives cannot be changed inside apache2.conf or virtualhost(s), you may check the documentation .