diff --git a/server/djrandom/frontend/frontend.py b/server/djrandom/frontend/frontend.py
index 8eaba9d306a0f1e1b6b23c762093bf1ea0dabb0c..2e1ca80ac10f333428b507f43c554f57d0b50cf5 100644
--- a/server/djrandom/frontend/frontend.py
+++ b/server/djrandom/frontend/frontend.py
@@ -7,59 +7,14 @@ import optparse
 import logging
 from djrandom import daemonize
 from djrandom import utils
-from djrandom.database import init_db
-from djrandom.frontend import app, svcs
-from djrandom.frontend.mailer import Mailer
-from djrandom.frontend.search import Searcher
-from djrandom.model.markov import MarkovModel
-from djrandom.model.external import AlbumImageRetriever
-from djrandom.frontend.latency import LatencyProfilerMiddleware
-from werkzeug.contrib.cache import SimpleCache, MemcachedCache
+from djrandom.frontend import wsgi_app
 from gevent.wsgi import WSGIServer
 
 log = logging.getLogger(__name__)
 
 
 def run_frontend(opts):
-    init_db(opts.db_url, opts.solr_url)
-
-    svcs['searcher'] = Searcher(opts.solr_url)
-    svcs['album_images'] = AlbumImageRetriever(
-        opts.lastfm_api_key, opts.album_art_dir)
-    svcs['mailer'] = Mailer(opts.email_sender)
-    if opts.memcached_url:
-        svcs['cache'] = MemcachedCache([opts.memcached_url])
-    else:
-        svcs['cache'] = SimpleCache()
-    svcs['markov'] = MarkovModel()
-    try:
-        svcs['markov'].load(opts.markov_data)
-    except IOError, e:
-        log.error('Could not read Markov data from %s: %s' % (
-                opts.markov_data, str(e)))
-
-    # Install our own latency profiler middleware.
-    app.wsgi_app = LatencyProfilerMiddleware(
-        app.wsgi_app,
-        ['/json/album', '/json/song', '/album_image',
-         '/json/playlist/get', '/json/playlist/by_title',
-         '/json/playlist/list', '/user/activate', '/dl'])
-
-    # Start the cProfile profiling middleware, if requested.
-    if opts.profile:
-        from repoze.profile.profiler import AccumulatingProfileMiddleware
-        app.wsgi_app = AccumulatingProfileMiddleware(
-            app.wsgi_app,
-            log_filename='/var/tmp/djrandom-profile.log',
-            cachegrind_filename='/var/tmp/djrandom-profile.cachegrind',
-            discard_first_request=True,
-            flush_at_shutdown=True,
-            path='/__profile__'
-            )
-
-    # Set some configuration options in the Flask global config.
-    app.config.from_pyfile(opts.app_config, silent=True)
-
+    app = wsgi_app.create_app(opts, True)
     http_server = WSGIServer((opts.bind_address, opts.port), app)
     http_server.serve_forever()
 
@@ -71,8 +26,9 @@ def main():
     parser.add_option('--port', type='int', default=3003)
     parser.add_option('--db_url')
     parser.add_option('--lastfm_api_key')
-    parser.add_option('--email_sender', default='djrandom@localhost')
-    parser.add_option('--album_art_dir', default='/var/tmp/album-image-cache')
+    parser.add_option('--email_sender', default='djrandom')
+    parser.add_option('--album_art_dir',
+                      default='/var/cache/djrandom/album-images')
     parser.add_option('--markov_data',
                       default='/var/lib/djrandom/djrandom-markov.dat')
     parser.add_option('--memcached_url')
diff --git a/server/djrandom/frontend/frontend.wsgi b/server/djrandom/frontend/frontend.wsgi
new file mode 100644
index 0000000000000000000000000000000000000000..82fa4d9c59d08ed213bf0f237f58413f16f9d766
--- /dev/null
+++ b/server/djrandom/frontend/frontend.wsgi
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+#
+# WSGI application file for Apache mod_wsgi.
+#
+
+
+import logging
+import os
+from djrandom import utils
+from djrandom.frontend import wsgi_app
+
+
+class _Config(object):
+
+    def __init__(self):
+        self.app_config = '/etc/djrandom-app.conf'
+        self.solr_url = 'http://localhost:8080/solr'
+        self.markov_data = '/var/lib/djrandom/djrandom-markov.dat'
+        self.album_art_dir = '/var/cache/djrandom/album-images'
+        self.email_sender = 'djrandom'
+        self.memcached_url = None
+        self.db_url = None
+        self.lastfm_api_key = None
+        self.profile = False
+
+    def set_default(self, key, value):
+        setattr(self, key, value)
+
+
+# Set up logging.
+# Messages will end up in the main Apache error.log file.
+logging.basicConfig(level=logging.INFO)
+
+# Read configuration from file.
+# Override the file location by setting DJRANDOM_CONF in the
+# mod_wsgi environment.
+_conf = _Config()
+utils.read_config_defaults(
+    _conf, os.getenv('DJRANDOM_CONF', '/etc/djrandom.conf'))
+if not _conf.db_url:
+    raise Exception('Must set db_url in DJRANDOM_CONF')
+
+# Create the WSGI application for mod_wsgi.
+application = wsgi_app.create_app(_conf)
diff --git a/server/djrandom/frontend/wsgi_app.py b/server/djrandom/frontend/wsgi_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..43d8b21bcd67264f36145cb5848106c1c7d61996
--- /dev/null
+++ b/server/djrandom/frontend/wsgi_app.py
@@ -0,0 +1,55 @@
+
+from djrandom.database import init_db
+from djrandom.frontend import app, svcs
+from djrandom.frontend.mailer import Mailer
+from djrandom.frontend.search import Searcher
+from djrandom.model.markov import MarkovModel
+from djrandom.model.external import AlbumImageRetriever
+from djrandom.frontend.latency import LatencyProfilerMiddleware
+from werkzeug.contrib.cache import SimpleCache, MemcachedCache
+
+log = logging.getLogger(__name__)
+
+
+def create_app(opts, latency_profiler=False):
+    init_db(opts.db_url, opts.solr_url)
+
+    svcs['searcher'] = Searcher(opts.solr_url)
+    svcs['album_images'] = AlbumImageRetriever(
+        opts.lastfm_api_key, opts.album_art_dir)
+    svcs['mailer'] = Mailer(opts.email_sender)
+    if opts.memcached_url:
+        svcs['cache'] = MemcachedCache([opts.memcached_url])
+    else:
+        svcs['cache'] = SimpleCache()
+    svcs['markov'] = MarkovModel()
+    try:
+        svcs['markov'].load(opts.markov_data)
+    except IOError, e:
+        log.error('Could not read Markov data from %s: %s' % (
+                opts.markov_data, str(e)))
+
+    if latency_profiler:
+        # Install our own latency profiler middleware.
+        app.wsgi_app = LatencyProfilerMiddleware(
+            app.wsgi_app,
+            ['/json/album', '/json/song', '/album_image',
+             '/json/playlist/get', '/json/playlist/by_title',
+             '/json/playlist/list', '/user/activate', '/dl'])
+
+    # Start the cProfile profiling middleware, if requested.
+    if opts.profile:
+        from repoze.profile.profiler import AccumulatingProfileMiddleware
+        app.wsgi_app = AccumulatingProfileMiddleware(
+            app.wsgi_app,
+            log_filename='/var/tmp/djrandom-profile.log',
+            cachegrind_filename='/var/tmp/djrandom-profile.cachegrind',
+            discard_first_request=True,
+            flush_at_shutdown=True,
+            path='/__profile__'
+            )
+
+    # Set some configuration options in the Flask global config.
+    app.config.from_pyfile(opts.app_config, silent=True)
+
+    return app