diff --git a/.fixtures.yml b/.fixtures.yml
index a3f4dbbb..d5ce9483 100644
--- a/.fixtures.yml
+++ b/.fixtures.yml
@@ -1,6 +1,8 @@
 fixtures:
   repositories:
+    apache: git://github.com/puppetlabs/puppetlabs-apache.git
     aviator: git://github.com/aimonb/puppet_aviator.git
+    concat: git://github.com/puppetlabs/puppetlabs-concat.git
     mysql: git://github.com/puppetlabs/puppetlabs-mysql.git
     postgresql: git://github.com/puppetlabs/puppetlabs-postgresql.git
     stdlib: git://github.com/puppetlabs/puppetlabs-stdlib.git
diff --git a/manifests/wsgi/apache.pp b/manifests/wsgi/apache.pp
new file mode 100644
index 00000000..469484aa
--- /dev/null
+++ b/manifests/wsgi/apache.pp
@@ -0,0 +1,189 @@
+#
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# Author: Emilien Macchi <emilien.macchi@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# == Class: openstacklib::wsgi::apache
+#
+# Serve a service with apache mod_wsgi
+# When using this class you should disable your service.
+#
+# == Parameters
+#
+# [*service_name*]
+#   (optional) Name of the service to run.
+#   Example: nova-api
+#   Defaults to $name
+#
+# [*servername*]
+#   (optional) The servername for the virtualhost.
+#   Defaults to $::fqdn
+#
+# [*bind_host*]
+#   (optional) The host/ip address Apache will listen on.
+#   Defaults to undef (listen on all ip addresses).
+#
+# [*bind_port*]
+#   (optional) The port to listen.
+#   Defaults to undef
+#
+# [*group*]
+#   (optional) Group with permissions on the script
+#   Defaults to undef
+#
+# [*path*]
+#   (optional) The prefix for the endpoint.
+#   Defaults to '/'
+#
+# [*priority*]
+#   (optional) The priority for the vhost.
+#   Defaults to '10'
+#
+# [*ssl*]
+#   (optional) Use ssl ? (boolean)
+#   Defaults to false
+#
+# [*ssl_cert*]
+#   (optional) Path to SSL certificate
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*ssl_key*]
+#   (optional) Path to SSL key
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*ssl_chain*]
+#   (optional) SSL chain
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*ssl_ca*]
+#   (optional) Path to SSL certificate authority
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*ssl_crl_path*]
+#   (optional) Path to SSL certificate revocation list
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*ssl_crl*]
+#   (optional) SSL certificate revocation list name
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*ssl_certs_dir*]
+#   (optional) Path to SSL certificate directory
+#   Default to apache::vhost 'ssl_*' defaults.
+#
+# [*threads*]
+#   (optional) The number of threads for the vhost.
+#   Defaults to $::processorcount
+#
+# [*user*]
+#   (optional) User with permissions on the script
+#   Defaults to undef
+#
+# [*workers*]
+#   (optional) The number of workers for the vhost.
+#   Defaults to '1'
+#
+# [*wsgi_script_path*]
+#   (optional) The path of the WSGI script.
+#   Defaults to undef
+#
+# [*wsgi_script_script*]
+#   (optional) The source of the WSGI script.
+#   Defaults to undef
+#
+define openstacklib::wsgi::apache (
+  $service_name       = $name,
+  $bind_host          = undef,
+  $bind_port          = undef,
+  $group              = undef,
+  $path               = '/',
+  $priority           = '10',
+  $servername         = $::fqdn,
+  $ssl                = false,
+  $ssl_ca             = undef,
+  $ssl_cert           = undef,
+  $ssl_certs_dir      = undef,
+  $ssl_chain          = undef,
+  $ssl_crl            = undef,
+  $ssl_crl_path       = undef,
+  $ssl_key            = undef,
+  $threads            = $::processorcount,
+  $user               = undef,
+  $workers            = 1,
+  $wsgi_script_path   = undef,
+  $wsgi_script_source = undef,
+) {
+
+  include ::apache
+  include ::apache::mod::wsgi
+  if $ssl {
+    include ::apache::mod::ssl
+  }
+
+  # Ensure there's no trailing '/' except if this is also the only character
+  $path_real = regsubst($path, '(^/.*)/$', '\1')
+
+  if !defined(File[$wsgi_script_path]) {
+    file { $wsgi_script_path:
+      ensure  => directory,
+      owner   => $user,
+      group   => $group,
+      require => Package['httpd'],
+    }
+  }
+
+  file { "${service_name}_wsgi":
+    ensure  => file,
+    path    => "${wsgi_script_path}/${service_name}_wsgi",
+    source  => $wsgi_script_source,
+    owner   => $user,
+    group   => $group,
+    mode    => '0644',
+    require => File[$wsgi_script_path],
+  }
+
+  $wsgi_daemon_process_options = {
+    owner     => $user,
+    group     => $group,
+    processes => $workers,
+    threads   => $threads,
+  }
+  $wsgi_script_aliases = hash([$path_real,$wsgi_script_path])
+
+  ::apache::vhost { "${service_name}_wsgi":
+    ensure                      => 'present',
+    servername                  => $servername,
+    ip                          => $bind_host,
+    port                        => $bind_port,
+    docroot                     => $wsgi_script_path,
+    docroot_owner               => $user,
+    docroot_group               => $group,
+    priority                    => $priority,
+    ssl                         => $ssl,
+    ssl_cert                    => $ssl_cert,
+    ssl_key                     => $ssl_key,
+    ssl_chain                   => $ssl_chain,
+    ssl_ca                      => $ssl_ca,
+    ssl_crl_path                => $ssl_crl_path,
+    ssl_crl                     => $ssl_crl,
+    ssl_certs_dir               => $ssl_certs_dir,
+    wsgi_daemon_process         => $service_name,
+    wsgi_daemon_process_options => $wsgi_daemon_process_options,
+    wsgi_process_group          => $service_name,
+    wsgi_script_aliases         => $wsgi_script_aliases,
+    require                     => File["${service_name}_wsgi"],
+  }
+
+}
diff --git a/metadata.json b/metadata.json
index ae6e3279..1b8a8322 100644
--- a/metadata.json
+++ b/metadata.json
@@ -32,6 +32,7 @@
   "description": "Puppet module library to expose common functionality between OpenStack modules.",
   "dependencies": [
     { "name": "aimonb/aviator", "version_requirement": ">=0.4.2 <1.0.0" },
+    { "name": "puppetlabs/apache", "version_requirement": ">=1.0.0 <2.0.0" },
     { "name": "puppetlabs/mysql", "version_requirement": ">=2.2.0 <3.0.0" },
     { "name": "puppetlabs/stdlib", "version_requirement": ">=4.0.0 <5.0.0" },
     { "name": "puppetlabs/rabbitmq", "version_requirement": ">=2.0.2 <4.0.0" },
diff --git a/spec/defines/openstacklib_wsgi_apache_spec.rb b/spec/defines/openstacklib_wsgi_apache_spec.rb
new file mode 100644
index 00000000..bc3522d1
--- /dev/null
+++ b/spec/defines/openstacklib_wsgi_apache_spec.rb
@@ -0,0 +1,120 @@
+#
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# Author: Emilien Macchi <emilien.macchi@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+require 'spec_helper'
+
+describe 'openstacklib::wsgi::apache' do
+
+  let (:title) { 'keystone' }
+
+  let :global_facts do
+    {
+      :processorcount => 42,
+      :concat_basedir => '/var/lib/puppet/concat',
+      :fqdn           => 'some.host.tld'
+    }
+  end
+
+  let :params do
+    {
+      :bind_port          => 5000,
+      :group              => 'keystone',
+      :ssl                => true,
+      :user               => 'keystone',
+      :wsgi_script_path   => '/var/www/cgi-bin/keystone',
+      :wsgi_script_source => '/usr/share/keystone/keystone.wsgi'
+    }
+  end
+
+  shared_examples_for 'apache serving a service with mod_wsgi' do
+    it { should contain_service('httpd').with_name(platform_parameters[:httpd_service_name]) }
+    it { should contain_class('apache') }
+    it { should contain_class('apache::mod::wsgi') }
+
+    describe 'with default parameters' do
+
+      it { should contain_file('/var/www/cgi-bin/keystone').with(
+        'ensure'  => 'directory',
+        'owner'   => 'keystone',
+        'group'   => 'keystone',
+        'require' => 'Package[httpd]'
+      )}
+
+      it { should contain_file('keystone_wsgi').with(
+        'ensure'  => 'file',
+        'path'    => '/var/www/cgi-bin/keystone/keystone_wsgi',
+        'source'  => '/usr/share/keystone/keystone.wsgi',
+        'owner'   => 'keystone',
+        'group'   => 'keystone',
+        'mode'    => '0644',
+      )}
+
+      it { should contain_apache__vhost('keystone_wsgi').with(
+        'servername'          => 'some.host.tld',
+        'ip'                  => nil,
+        'port'                => '5000',
+        'docroot'             => '/var/www/cgi-bin/keystone',
+        'docroot_owner'       => 'keystone',
+        'docroot_group'       => 'keystone',
+        'ssl'                 => 'true',
+        'wsgi_daemon_process' => 'keystone',
+        'wsgi_process_group'  => 'keystone',
+        'wsgi_script_aliases' => { '/' => "/var/www/cgi-bin/keystone" },
+        'require'             => 'File[keystone_wsgi]'
+      )}
+      it { should contain_file("#{platform_parameters[:httpd_ports_file]}") }
+    end
+
+  end
+
+  context 'on RedHat platforms' do
+    let :facts do
+      global_facts.merge({
+        :osfamily               => 'RedHat',
+        :operatingsystemrelease => '7.0'
+      })
+    end
+
+    let :platform_parameters do
+      {
+        :httpd_service_name => 'httpd',
+        :httpd_ports_file   => '/etc/httpd/conf/ports.conf',
+      }
+    end
+
+    it_configures 'apache serving a service with mod_wsgi'
+  end
+
+  context 'on Debian platforms' do
+    let :facts do
+      global_facts.merge({
+        :osfamily               => 'Debian',
+        :operatingsystem        => 'Debian',
+        :operatingsystemrelease => '7.0'
+      })
+    end
+
+    let :platform_parameters do
+      {
+        :httpd_service_name => 'apache2',
+        :httpd_ports_file   => '/etc/apache2/ports.conf',
+      }
+    end
+
+    it_configures 'apache serving a service with mod_wsgi'
+  end
+end
diff --git a/spec/shared_examples.rb b/spec/shared_examples.rb
new file mode 100644
index 00000000..d92156a3
--- /dev/null
+++ b/spec/shared_examples.rb
@@ -0,0 +1,5 @@
+shared_examples_for "a Puppet::Error" do |description|
+  it "with message matching #{description.inspect}" do
+    expect { should have_class_count(1) }.to raise_error(Puppet::Error, description)
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ecd609ae..4f919c0f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,6 +1,13 @@
 require 'puppetlabs_spec_helper/module_spec_helper'
+require 'shared_examples'
 require 'vcr'
 
+
+RSpec.configure do |c|
+  c.alias_it_should_behave_like_to :it_configures, 'configures'
+  c.alias_it_should_behave_like_to :it_raises, 'raises'
+end
+
 VCR.configure do |c|
   c.cassette_library_dir = 'spec/fixtures/vcr'
   c.hook_into :faraday