diff --git a/docs/async-await.rst b/docs/async-await.rst
index 4c70f961..cea84602 100644
--- a/docs/async-await.rst
+++ b/docs/async-await.rst
@@ -70,8 +70,8 @@ If you wish to use background tasks it is best to use a task queue to
trigger background work, rather than spawn tasks in a view
function. With that in mind you can spawn asyncio tasks by serving
Flask with an ASGI server and utilising the asgiref WsgiToAsgi adapter
-as described in :ref:`asgi`. This works as the adapter creates an
-event loop that runs continually.
+as described in :doc:`deploying/asgi`. This works as the adapter creates
+an event loop that runs continually.
When to use Quart instead
diff --git a/docs/deploying/apache-httpd.rst b/docs/deploying/apache-httpd.rst
new file mode 100644
index 00000000..bdeaf626
--- /dev/null
+++ b/docs/deploying/apache-httpd.rst
@@ -0,0 +1,66 @@
+Apache httpd
+============
+
+`Apache httpd`_ is a fast, production level HTTP server. When serving
+your application with one of the WSGI servers listed in :doc:`index`, it
+is often good or necessary to put a dedicated HTTP server in front of
+it. This "reverse proxy" can handle incoming requests, TLS, and other
+security and performance concerns better than the WSGI server.
+
+httpd can be installed using your system package manager, or a pre-built
+executable for Windows. Installing and running httpd itself is outside
+the scope of this doc. This page outlines the basics of configuring
+httpd to proxy your application. Be sure to read its documentation to
+understand what features are available.
+
+.. _Apache httpd: https://httpd.apache.org/
+
+
+Domain Name
+-----------
+
+Acquiring and configuring a domain name is outside the scope of this
+doc. In general, you will buy a domain name from a registrar, pay for
+server space with a hosting provider, and then point your registrar
+at the hosting provider's name servers.
+
+To simulate this, you can also edit your ``hosts`` file, located at
+``/etc/hosts`` on Linux. Add a line that associates a name with the
+local IP.
+
+Modern Linux systems may be configured to treat any domain name that
+ends with ``.localhost`` like this without adding it to the ``hosts``
+file.
+
+.. code-block:: python
+ :caption: ``/etc/hosts``
+
+ 127.0.0.1 hello.localhost
+
+
+Configuration
+-------------
+
+The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on
+Linux. It may be different depending on your operating system. Check the
+docs and look for ``httpd.conf``.
+
+Remove or comment out any existing ``DocumentRoot`` directive. Add the
+config lines below. We'll assume the WSGI server is listening locally at
+``http://127.0.0.1:8000``.
+
+.. code-block:: apache
+ :caption: ``/etc/httpd/conf/httpd.conf``
+
+ LoadModule proxy_module modules/mod_proxy.so
+ LoadModule proxy_http_module modules/mod_proxy_http.so
+ ProxyPass / http://127.0.0.1:8000/
+ RequestHeader set X-Forwarded-Proto http
+ RequestHeader set X-Forwarded-Prefix /
+
+The ``LoadModule`` lines might already exist. If so, make sure they are
+uncommented instead of adding them manually.
+
+Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded``
+headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically
+set by ``ProxyPass``.
diff --git a/docs/deploying/asgi.rst b/docs/deploying/asgi.rst
index 39cd76b7..36acff8a 100644
--- a/docs/deploying/asgi.rst
+++ b/docs/deploying/asgi.rst
@@ -1,5 +1,3 @@
-.. _asgi:
-
ASGI
====
diff --git a/docs/deploying/cgi.rst b/docs/deploying/cgi.rst
deleted file mode 100644
index 4c1cdfbf..00000000
--- a/docs/deploying/cgi.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-CGI
-===
-
-If all other deployment methods do not work, CGI will work for sure.
-CGI is supported by all major servers but usually has a sub-optimal
-performance.
-
-This is also the way you can use a Flask application on Google's `App
-Engine`_, where execution happens in a CGI-like environment.
-
-.. admonition:: Watch Out
-
- Please make sure in advance that any ``app.run()`` calls you might
- have in your application file are inside an ``if __name__ ==
- '__main__':`` block or moved to a separate file. Just make sure it's
- not called because this will always start a local WSGI server which
- we do not want if we deploy that application to CGI / app engine.
-
- With CGI, you will also have to make sure that your code does not contain
- any ``print`` statements, or that ``sys.stdout`` is overridden by something
- that doesn't write into the HTTP response.
-
-Creating a `.cgi` file
-----------------------
-
-First you need to create the CGI application file. Let's call it
-:file:`yourapplication.cgi`::
-
- #!/usr/bin/python
- from wsgiref.handlers import CGIHandler
- from yourapplication import app
-
- CGIHandler().run(app)
-
-Server Setup
-------------
-
-Usually there are two ways to configure the server. Either just copy the
-``.cgi`` into a :file:`cgi-bin` (and use `mod_rewrite` or something similar to
-rewrite the URL) or let the server point to the file directly.
-
-In Apache for example you can put something like this into the config:
-
-.. sourcecode:: apache
-
- ScriptAlias /app /path/to/the/application.cgi
-
-On shared webhosting, though, you might not have access to your Apache config.
-In this case, a file called ``.htaccess``, sitting in the public directory
-you want your app to be available, works too but the ``ScriptAlias`` directive
-won't work in that case:
-
-.. sourcecode:: apache
-
- RewriteEngine On
- RewriteCond %{REQUEST_FILENAME} !-f # Don't interfere with static files
- RewriteRule ^(.*)$ /path/to/the/application.cgi/$1 [L]
-
-For more information consult the documentation of your webserver.
-
-.. _App Engine: https://cloud.google.com/appengine/docs/
diff --git a/docs/deploying/eventlet.rst b/docs/deploying/eventlet.rst
new file mode 100644
index 00000000..8842ce05
--- /dev/null
+++ b/docs/deploying/eventlet.rst
@@ -0,0 +1,80 @@
+eventlet
+========
+
+Prefer using :doc:`gunicorn` with eventlet workers rather than using
+`eventlet`_ directly. Gunicorn provides a much more configurable and
+production-tested server.
+
+`eventlet`_ allows writing asynchronous, coroutine-based code that looks
+like standard synchronous Python. It uses `greenlet`_ to enable task
+switching without writing ``async/await`` or using ``asyncio``.
+
+:doc:`gevent` is another library that does the same thing. Certain
+dependencies you have, or other considerations, may affect which of the
+two you choose to use.
+
+eventlet provides a WSGI server that can handle many connections at once
+instead of one per worker process. You must actually use eventlet in
+your own code to see any benefit to using the server.
+
+.. _eventlet: https://eventlet.net/
+.. _greenlet: https://greenlet.readthedocs.io/en/latest/
+
+
+Installing
+----------
+
+When using eventlet, greenlet>=1.0 is required, otherwise context locals
+such as ``request`` will not work as expected. When using PyPy,
+PyPy>=7.3.7 is required.
+
+Create a virtualenv, install your application, then install
+``eventlet``.
+
+.. code-block:: text
+
+ $ cd hello-app
+ $ python -m venv venv
+ $ . venv/bin/activate
+ $ pip install . # install your application
+ $ pip install eventlet
+
+
+Running
+-------
+
+To use eventlet to serve your application, write a script that imports
+its ```wsgi.server``, as well as your app or app factory.
+
+.. code-block:: python
+ :caption: ``wsgi.py``
+
+ import eventlet
+ from eventlet import wsgi
+ from hello import create_app
+
+ app = create_app()
+ wsgi.server(eventlet.listen(("127.0.0.1", 8000), app)
+
+.. code-block:: text
+
+ $ python wsgi.py
+ (x) wsgi starting up on http://127.0.0.1:8000
+
+
+Binding Externally
+------------------
+
+eventlet should not be run as root because it would cause your
+application code to run as root, which is not secure. However, this
+means it will not be possible to bind to port 80 or 443. Instead, a
+reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
+in front of eventlet.
+
+You can bind to all external IPs on a non-privileged port by using
+``0.0.0.0`` in the server arguments shown in the previous section.
+Don't do this when using a reverse proxy setup, otherwise it will be
+possible to bypass the proxy.
+
+``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
+IP address in your browser.
diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst
deleted file mode 100644
index d3614d37..00000000
--- a/docs/deploying/fastcgi.rst
+++ /dev/null
@@ -1,238 +0,0 @@
-FastCGI
-=======
-
-FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
-`cherokee`_; see :doc:`uwsgi` and :doc:`wsgi-standalone` for other options.
-To use your WSGI application with any of them you will need a FastCGI
-server first. The most popular one is `flup`_ which we will use for
-this guide. Make sure to have it installed to follow along.
-
-.. admonition:: Watch Out
-
- Please make sure in advance that any ``app.run()`` calls you might
- have in your application file are inside an ``if __name__ ==
- '__main__':`` block or moved to a separate file. Just make sure it's
- not called because this will always start a local WSGI server which
- we do not want if we deploy that application to FastCGI.
-
-Creating a `.fcgi` file
------------------------
-
-First you need to create the FastCGI server file. Let's call it
-`yourapplication.fcgi`::
-
- #!/usr/bin/python
- from flup.server.fcgi import WSGIServer
- from yourapplication import app
-
- if __name__ == '__main__':
- WSGIServer(app).run()
-
-This is enough for Apache to work, however nginx and older versions of
-lighttpd need a socket to be explicitly passed to communicate with the
-FastCGI server. For that to work you need to pass the path to the
-socket to the :class:`~flup.server.fcgi.WSGIServer`::
-
- WSGIServer(application, bindAddress='/path/to/fcgi.sock').run()
-
-The path has to be the exact same path you define in the server
-config.
-
-Save the :file:`yourapplication.fcgi` file somewhere you will find it again.
-It makes sense to have that in :file:`/var/www/yourapplication` or something
-similar.
-
-Make sure to set the executable bit on that file so that the servers
-can execute it:
-
-.. sourcecode:: text
-
- $ chmod +x /var/www/yourapplication/yourapplication.fcgi
-
-Configuring Apache
-------------------
-
-The example above is good enough for a basic Apache deployment but your
-`.fcgi` file will appear in your application URL e.g.
-``example.com/yourapplication.fcgi/news/``. There are few ways to configure
-your application so that yourapplication.fcgi does not appear in the URL.
-A preferable way is to use the ScriptAlias and SetHandler configuration
-directives to route requests to the FastCGI server. The following example
-uses FastCgiServer to start 5 instances of the application which will
-handle all incoming requests::
-
- LoadModule fastcgi_module /usr/lib64/httpd/modules/mod_fastcgi.so
-
- FastCgiServer /var/www/html/yourapplication/app.fcgi -idle-timeout 300 -processes 5
-
-
- ServerName webapp1.mydomain.com
- DocumentRoot /var/www/html/yourapplication
-
- AddHandler fastcgi-script fcgi
- ScriptAlias / /var/www/html/yourapplication/app.fcgi/
-
-
- SetHandler fastcgi-script
-
-
-
-These processes will be managed by Apache. If you're using a standalone
-FastCGI server, you can use the FastCgiExternalServer directive instead.
-Note that in the following the path is not real, it's simply used as an
-identifier to other
-directives such as AliasMatch::
-
- FastCgiServer /var/www/html/yourapplication -host 127.0.0.1:3000
-
-If you cannot set ScriptAlias, for example on a shared web host, you can use
-WSGI middleware to remove yourapplication.fcgi from the URLs. Set .htaccess::
-
-
- AddHandler fcgid-script .fcgi
-
- SetHandler fcgid-script
- Options +FollowSymLinks +ExecCGI
-
-
-
-
- Options +FollowSymlinks
- RewriteEngine On
- RewriteBase /
- RewriteCond %{REQUEST_FILENAME} !-f
- RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L]
-
-
-Set yourapplication.fcgi::
-
- #!/usr/bin/python
- #: optional path to your local python site-packages folder
- import sys
- sys.path.insert(0, '/lib/python/site-packages')
-
- from flup.server.fcgi import WSGIServer
- from yourapplication import app
-
- class ScriptNameStripper(object):
- def __init__(self, app):
- self.app = app
-
- def __call__(self, environ, start_response):
- environ['SCRIPT_NAME'] = ''
- return self.app(environ, start_response)
-
- app = ScriptNameStripper(app)
-
- if __name__ == '__main__':
- WSGIServer(app).run()
-
-Configuring lighttpd
---------------------
-
-A basic FastCGI configuration for lighttpd looks like that::
-
- fastcgi.server = ("/yourapplication.fcgi" =>
- ((
- "socket" => "/tmp/yourapplication-fcgi.sock",
- "bin-path" => "/var/www/yourapplication/yourapplication.fcgi",
- "check-local" => "disable",
- "max-procs" => 1
- ))
- )
-
- alias.url = (
- "/static/" => "/path/to/your/static/"
- )
-
- url.rewrite-once = (
- "^(/static($|/.*))$" => "$1",
- "^(/.*)$" => "/yourapplication.fcgi$1"
- )
-
-Remember to enable the FastCGI, alias and rewrite modules. This configuration
-binds the application to ``/yourapplication``. If you want the application to
-work in the URL root you have to work around a lighttpd bug with the
-:class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware.
-
-Make sure to apply it only if you are mounting the application the URL
-root. Also, see the Lighty docs for more information on `FastCGI and Python
-`_ (note that
-explicitly passing a socket to run() is no longer necessary).
-
-Configuring nginx
------------------
-
-Installing FastCGI applications on nginx is a bit different because by
-default no FastCGI parameters are forwarded.
-
-A basic Flask FastCGI configuration for nginx looks like this::
-
- location = /yourapplication { rewrite ^ /yourapplication/ last; }
- location /yourapplication { try_files $uri @yourapplication; }
- location @yourapplication {
- include fastcgi_params;
- fastcgi_split_path_info ^(/yourapplication)(.*)$;
- fastcgi_param PATH_INFO $fastcgi_path_info;
- fastcgi_param SCRIPT_NAME $fastcgi_script_name;
- fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
- }
-
-This configuration binds the application to ``/yourapplication``. If you
-want to have it in the URL root it's a bit simpler because you don't
-have to figure out how to calculate ``PATH_INFO`` and ``SCRIPT_NAME``::
-
- location / { try_files $uri @yourapplication; }
- location @yourapplication {
- include fastcgi_params;
- fastcgi_param PATH_INFO $fastcgi_script_name;
- fastcgi_param SCRIPT_NAME "";
- fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
- }
-
-Running FastCGI Processes
--------------------------
-
-Since nginx and others do not load FastCGI apps, you have to do it by
-yourself. `Supervisor can manage FastCGI processes.
-`_
-You can look around for other FastCGI process managers or write a script
-to run your `.fcgi` file at boot, e.g. using a SysV ``init.d`` script.
-For a temporary solution, you can always run the ``.fcgi`` script inside
-GNU screen. See ``man screen`` for details, and note that this is a
-manual solution which does not persist across system restart::
-
- $ screen
- $ /var/www/yourapplication/yourapplication.fcgi
-
-Debugging
----------
-
-FastCGI deployments tend to be hard to debug on most web servers. Very
-often the only thing the server log tells you is something along the
-lines of "premature end of headers". In order to debug the application
-the only thing that can really give you ideas why it breaks is switching
-to the correct user and executing the application by hand.
-
-This example assumes your application is called `application.fcgi` and
-that your web server user is `www-data`::
-
- $ su www-data
- $ cd /var/www/yourapplication
- $ python application.fcgi
- Traceback (most recent call last):
- File "yourapplication.fcgi", line 4, in
- ImportError: No module named yourapplication
-
-In this case the error seems to be "yourapplication" not being on the
-python path. Common problems are:
-
-- Relative paths being used. Don't rely on the current working directory.
-- The code depending on environment variables that are not set by the
- web server.
-- Different python interpreters being used.
-
-.. _nginx: https://nginx.org/
-.. _lighttpd: https://www.lighttpd.net/
-.. _cherokee: https://cherokee-project.com/
-.. _flup: https://pypi.org/project/flup/
diff --git a/docs/deploying/gevent.rst b/docs/deploying/gevent.rst
new file mode 100644
index 00000000..aae63e89
--- /dev/null
+++ b/docs/deploying/gevent.rst
@@ -0,0 +1,80 @@
+gevent
+======
+
+Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather
+than using `gevent`_ directly. Gunicorn and uWSGI provide much more
+configurable and production-tested servers.
+
+`gevent`_ allows writing asynchronous, coroutine-based code that looks
+like standard synchronous Python. It uses `greenlet`_ to enable task
+switching without writing ``async/await`` or using ``asyncio``.
+
+:doc:`eventlet` is another library that does the same thing. Certain
+dependencies you have, or other considerations, may affect which of the
+two you choose to use.
+
+gevent provides a WSGI server that can handle many connections at once
+instead of one per worker process. You must actually use gevent in your
+own code to see any benefit to using the server.
+
+.. _gevent: https://www.gevent.org/
+.. _greenlet: https://greenlet.readthedocs.io/en/latest/
+
+
+Installing
+----------
+
+When using gevent, greenlet>=1.0 is required, otherwise context locals
+such as ``request`` will not work as expected. When using PyPy,
+PyPy>=7.3.7 is required.
+
+Create a virtualenv, install your application, then install ``gevent``.
+
+.. code-block:: text
+
+ $ cd hello-app
+ $ python -m venv venv
+ $ . venv/bin/activate
+ $ pip install . # install your application
+ $ pip install gevent
+
+
+Running
+-------
+
+To use gevent to serve your application, write a script that imports its
+``WSGIServer``, as well as your app or app factory.
+
+.. code-block:: python
+ :caption: ``wsgi.py``
+
+ from gevent.pywsgi import WSGIServer
+ from hello import create_app
+
+ app = create_app()
+ http_server = WSGIServer(("127.0.0.1", 8000), app)
+ http_server.serve_forever()
+
+.. code-block:: text
+
+ $ python wsgi.py
+
+No output is shown when the server starts.
+
+
+Binding Externally
+------------------
+
+gevent should not be run as root because it would cause your
+application code to run as root, which is not secure. However, this
+means it will not be possible to bind to port 80 or 443. Instead, a
+reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
+in front of gevent.
+
+You can bind to all external IPs on a non-privileged port by using
+``0.0.0.0`` in the server arguments shown in the previous section. Don't
+do this when using a reverse proxy setup, otherwise it will be possible
+to bypass the proxy.
+
+``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
+IP address in your browser.
diff --git a/docs/deploying/gunicorn.rst b/docs/deploying/gunicorn.rst
new file mode 100644
index 00000000..93d11d39
--- /dev/null
+++ b/docs/deploying/gunicorn.rst
@@ -0,0 +1,130 @@
+Gunicorn
+========
+
+`Gunicorn`_ is a pure Python WSGI server with simple configuration and
+multiple worker implementations for performance tuning.
+
+* It tends to integrate easily with hosting platforms.
+* It does not support Windows (but does run on WSL).
+* It is easy to install as it does not require additional dependencies
+ or compilation.
+* It has built-in async worker support using gevent or eventlet.
+
+This page outlines the basics of running Gunicorn. Be sure to read its
+`documentation`_ and use ``gunicorn --help`` to understand what features
+are available.
+
+.. _Gunicorn: https://gunicorn.org/
+.. _documentation: https://docs.gunicorn.org/
+
+
+Installing
+----------
+
+Gunicorn is easy to install, as it does not require external
+dependencies or compilation. It runs on Windows only under WSL.
+
+Create a virtualenv, install your application, then install
+``gunicorn``.
+
+.. code-block:: text
+
+ $ cd hello-app
+ $ python -m venv venv
+ $ . venv/bin/activate
+ $ pip install . # install your application
+ $ pip install gunicorn
+
+
+Running
+-------
+
+The only required argument to Gunicorn tells it how to load your Flask
+application. The syntax is ``{module_import}:{app_variable}``.
+``module_import`` is the dotted import name to the module with your
+application. ``app_variable`` is the variable with the application. It
+can also be a function call (with any arguments) if you're using the
+app factory pattern.
+
+.. code-block:: text
+
+ # equivalent to 'from hello import app'
+ $ gunicorn -w 4 'hello:app'
+
+ # equivalent to 'from hello import create_app; create_app()'
+ $ gunicorn -w 4 'hello:create_app()'
+
+ Starting gunicorn 20.1.0
+ Listening at: http://127.0.0.1:8000 (x)
+ Using worker: sync
+ Booting worker with pid: x
+ Booting worker with pid: x
+ Booting worker with pid: x
+ Booting worker with pid: x
+
+The ``-w`` option specifies the number of processes to run; a starting
+value could be ``CPU * 2``. The default is only 1 worker, which is
+probably not what you want for the default worker type.
+
+Logs for each request aren't shown by default, only worker info and
+errors are shown. To show access logs on stdout, use the
+``--access-logfile=-`` option.
+
+
+Binding Externally
+------------------
+
+Gunicorn should not be run as root because it would cause your
+application code to run as root, which is not secure. However, this
+means it will not be possible to bind to port 80 or 443. Instead, a
+reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
+in front of Gunicorn.
+
+You can bind to all external IPs on a non-privileged port using the
+``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup,
+otherwise it will be possible to bypass the proxy.
+
+.. code-block:: text
+
+ $ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()'
+ Listening at: http://0.0.0.0:8000 (x)
+
+``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
+IP address in your browser.
+
+
+Async with gevent or eventlet
+-----------------------------
+
+The default sync worker is appropriate for many use cases. If you need
+asynchronous support, Gunicorn provides workers using either `gevent`_
+or `eventlet`_. This is not the same as Python's ``async/await``, or the
+ASGI server spec. You must actually use gevent/eventlet in your own code
+to see any benefit to using the workers.
+
+When using either gevent or eventlet, greenlet>=1.0 is required,
+otherwise context locals such as ``request`` will not work as expected.
+When using PyPy, PyPy>=7.3.7 is required.
+
+To use gevent:
+
+.. code-block:: text
+
+ $ gunicorn -k gevent 'hello:create_app()'
+ Starting gunicorn 20.1.0
+ Listening at: http://127.0.0.1:8000 (x)
+ Using worker: gevent
+ Booting worker with pid: x
+
+To use eventlet:
+
+.. code-block:: text
+
+ $ gunicorn -k eventlet 'hello:create_app()'
+ Starting gunicorn 20.1.0
+ Listening at: http://127.0.0.1:8000 (x)
+ Using worker: eventlet
+ Booting worker with pid: x
+
+.. _gevent: https://www.gevent.org/
+.. _eventlet: https://eventlet.net/
diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst
index e1ed9269..2e48682a 100644
--- a/docs/deploying/index.rst
+++ b/docs/deploying/index.rst
@@ -1,35 +1,80 @@
-Deployment Options
-==================
+Deploying to Production
+=======================
-While lightweight and easy to use, **Flask's built-in server is not suitable
-for production** as it doesn't scale well. Some of the options available for
-properly running Flask in production are documented here.
+After developing your application, you'll want to make it available
+publicly to other users. When you're developing locally, you're probably
+using the built-in development server, debugger, and reloader. These
+should not be used in production. Instead, you should use a dedicated
+WSGI server or hosting platform, some of which will be described here.
-If you want to deploy your Flask application to a WSGI server not listed here,
-look up the server documentation about how to use a WSGI app with it. Just
-remember that your :class:`Flask` application object is the actual WSGI
-application.
+"Production" means "not development", which applies whether you're
+serving your application publicly to millions of users or privately /
+locally to a single user. **Do not use the development server when
+deploying to production. It is intended for use only during local
+development. It is not designed to be particularly secure, stable, or
+efficient.**
-
-Hosted options
---------------
-
-- `Deploying Flask on Heroku `_
-- `Deploying Flask on Google App Engine `_
-- `Deploying Flask on Google Cloud Run `_
-- `Deploying Flask on AWS Elastic Beanstalk `_
-- `Deploying on Azure (IIS) `_
-- `Deploying on PythonAnywhere `_
-
-Self-hosted options
+Self-Hosted Options
-------------------
-.. toctree::
- :maxdepth: 2
+Flask is a WSGI *application*. A WSGI *server* is used to run the
+application, converting incoming HTTP requests to the standard WSGI
+environ, and converting outgoing WSGI responses to HTTP responses.
- wsgi-standalone
- uwsgi
- mod_wsgi
- fastcgi
- cgi
- asgi
+The primary goal of these docs is to familiarize you with the concepts
+involved in running a WSGI application using a production WSGI server
+and HTTP server. There are many WSGI servers and HTTP servers, with many
+configuration possibilities. The pages below discuss the most common
+servers, and show the basics of running each one. The next section
+discusses platforms that can manage this for you.
+
+.. toctree::
+ :maxdepth: 1
+
+ gunicorn
+ waitress
+ mod_wsgi
+ uwsgi
+ gevent
+ eventlet
+ asgi
+
+WSGI servers have HTTP servers built-in. However, a dedicated HTTP
+server may be safer, more efficient, or more capable. Putting an HTTP
+server in front of the WSGI server is called a "reverse proxy."
+
+.. toctree::
+ :maxdepth: 1
+
+ proxy_fix
+ nginx
+ apache-httpd
+
+This list is not exhaustive, and you should evaluate these and other
+servers based on your application's needs. Different servers will have
+different capabilities, configuration, and support.
+
+
+Hosting Platforms
+-----------------
+
+There are many services available for hosting web applications without
+needing to maintain your own server, networking, domain, etc. Some
+services may have a free tier up to a certain time or bandwidth. Many of
+these services use one of the WSGI servers described above, or a similar
+interface. The links below are for some of the most common platforms,
+which have instructions for Flask, WSGI, or Python.
+
+- `PythonAnywhere `_
+- `Heroku `_
+- `Google App Engine `_
+- `Google Cloud Run `_
+- `AWS Elastic Beanstalk `_
+- `Microsoft Azure `_
+
+This list is not exhaustive, and you should evaluate these and other
+services based on your application's needs. Different services will have
+different capabilities, configuration, pricing, and support.
+
+You'll probably need to :doc:`proxy_fix` when using most hosting
+platforms.
diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst
index 801dfa5c..5ae5e2cf 100644
--- a/docs/deploying/mod_wsgi.rst
+++ b/docs/deploying/mod_wsgi.rst
@@ -1,216 +1,105 @@
-mod_wsgi (Apache)
-=================
+mod_wsgi
+========
-If you are using the `Apache`_ webserver, consider using `mod_wsgi`_.
+`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server.
+The modern `mod_wsgi-express`_ command makes it easy to configure and
+start the server without needing to write Apache httpd configuration.
-.. admonition:: Watch Out
+* Tightly integrated with Apache httpd.
+* Supports Windows directly.
+* Requires a compiler to install. Requires Apache installed separately
+ on Windows.
+* Does not require a reverse proxy setup.
- Please make sure in advance that any ``app.run()`` calls you might
- have in your application file are inside an ``if __name__ ==
- '__main__':`` block or moved to a separate file. Just make sure it's
- not called because this will always start a local WSGI server which
- we do not want if we deploy that application to mod_wsgi.
+This page outlines the basics of running mod_wsgi-express, not the more
+complex installation and configuration with httpd. Be sure to read the
+`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to
+understand what features are available.
-.. _Apache: https://httpd.apache.org/
+.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/
+.. _mod_wsgi: https://modwsgi.readthedocs.io/
+.. _Apache httpd: https://httpd.apache.org/
-Installing `mod_wsgi`
----------------------
-If you don't have `mod_wsgi` installed yet you have to either install it
-using a package manager or compile it yourself. The mod_wsgi
-`installation instructions`_ cover source installations on UNIX systems.
+Installing
+----------
-If you are using Ubuntu/Debian you can apt-get it and activate it as
-follows:
+On Linux/Mac, the most straightforward way to install mod_wsgi is to
+install the ``mod_wsgi-standalone`` package, which will compile an
+up-to-date version of Apache httpd as well.
-.. sourcecode:: text
+Create a virtualenv, install your application, then install
+``mod_wsgi-standalone``.
- $ apt-get install libapache2-mod-wsgi-py3
+.. code-block:: text
-If you are using a yum based distribution (Fedora, OpenSUSE, etc..) you
-can install it as follows:
+ $ cd hello-app
+ $ python -m venv venv
+ $ . venv/bin/activate
+ $ pip install . # install your application
+ $ pip install mod_wsgi-standalone
-.. sourcecode:: text
+If you want to use the system-installed version of Apache httpd
+(required on Windows, optional but faster on Linux/Mac), install the
+``mod_wsgi`` package instead. You will get an error if Apache and its
+development headers are not available. How to install them depends on
+what OS and package manager you use.
- $ yum install mod_wsgi
+.. code-block:: text
-On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by
-using pkg_add:
+ $ pip install mod_wsgi
-.. sourcecode:: text
- $ pkg install ap24-py37-mod_wsgi
+Running
+-------
-If you are using pkgsrc you can install `mod_wsgi` by compiling the
-`www/ap2-wsgi` package.
+The only argument to ``mod_wsgi-express`` specifies a script containing
+your Flask application, which must be called ``application``. You can
+write a small script to import your app with this name, or to create it
+if using the app factory pattern.
-If you encounter segfaulting child processes after the first apache
-reload you can safely ignore them. Just restart the server.
+.. code-block:: python
+ :caption: ``wsgi.py``
-Creating a `.wsgi` file
------------------------
+ from hello import app
-To run your application you need a :file:`yourapplication.wsgi` file.
-This file contains the code `mod_wsgi` is executing on startup
-to get the application object. The object called `application`
-in that file is then used as application.
+ application = app
-For most applications the following file should be sufficient::
+.. code-block:: python
+ :caption: ``wsgi.py``
- from yourapplication import app as application
+ from hello import create_app
-If a factory function is used in a :file:`__init__.py` file, then the function should be imported::
-
- from yourapplication import create_app
application = create_app()
-If you don't have a factory function for application creation but a singleton
-instance you can directly import that one as `application`.
+Now run the ``mod_wsgi-express start-server`` command.
-Store that file somewhere that you will find it again (e.g.:
-:file:`/var/www/yourapplication`) and make sure that `yourapplication` and all
-the libraries that are in use are on the python load path. If you don't
-want to install it system wide consider using a `virtual python`_
-instance. Keep in mind that you will have to actually install your
-application into the virtualenv as well. Alternatively there is the
-option to just patch the path in the ``.wsgi`` file before the import::
+.. code-block:: text
- import sys
- sys.path.insert(0, '/path/to/the/application')
+ $ mod_wsgi-express start-server wsgi.py --processes 4
-Configuring Apache
+The ``--processes`` option specifies the number of worker processes to
+run; a starting value could be ``CPU * 2``.
+
+Logs for each request aren't show in the terminal. If an error occurs,
+its information is written to the error log file shown when starting the
+server.
+
+
+Binding Externally
------------------
-The last thing you have to do is to create an Apache configuration file
-for your application. In this example we are telling `mod_wsgi` to
-execute the application under a different user for security reasons:
+Unlike the other WSGI servers in these docs, mod_wsgi can be run as
+root to bind to privileged ports like 80 and 443. However, it must be
+configured to drop permissions to a different user and group for the
+worker processes.
-.. sourcecode:: apache
+For example, if you created a ``hello`` user and group, you should
+install your virtualenv and application as that user, then tell
+mod_wsgi to drop to that user after starting.
-
- ServerName example.com
+.. code-block:: text
- WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5
- WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi
-
-
- WSGIProcessGroup yourapplication
- WSGIApplicationGroup %{GLOBAL}
- Order deny,allow
- Allow from all
-
-
-
-Note: WSGIDaemonProcess isn't implemented in Windows and Apache will
-refuse to run with the above configuration. On a Windows system, eliminate those lines:
-
-.. sourcecode:: apache
-
-
- ServerName example.com
- WSGIScriptAlias / C:\yourdir\yourapp.wsgi
-
- Order deny,allow
- Allow from all
-
-
-
-Note: There have been some changes in access control configuration
-for `Apache 2.4`_.
-
-.. _Apache 2.4: https://httpd.apache.org/docs/trunk/upgrading.html
-
-Most notably, the syntax for directory permissions has changed from httpd 2.2
-
-.. sourcecode:: apache
-
- Order allow,deny
- Allow from all
-
-to httpd 2.4 syntax
-
-.. sourcecode:: apache
-
- Require all granted
-
-
-For more information consult the `mod_wsgi documentation`_.
-
-.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
-.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html
-.. _virtual python: https://pypi.org/project/virtualenv/
-.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html
-
-Troubleshooting
----------------
-
-If your application does not run, follow this guide to troubleshoot:
-
-**Problem:** application does not run, errorlog shows SystemExit ignored
- You have an ``app.run()`` call in your application file that is not
- guarded by an ``if __name__ == '__main__':`` condition. Either
- remove that :meth:`~flask.Flask.run` call from the file and move it
- into a separate :file:`run.py` file or put it into such an if block.
-
-**Problem:** application gives permission errors
- Probably caused by your application running as the wrong user. Make
- sure the folders the application needs access to have the proper
- privileges set and the application runs as the correct user
- (``user`` and ``group`` parameter to the `WSGIDaemonProcess`
- directive)
-
-**Problem:** application dies with an error on print
- Keep in mind that mod_wsgi disallows doing anything with
- :data:`sys.stdout` and :data:`sys.stderr`. You can disable this
- protection from the config by setting the `WSGIRestrictStdout` to
- ``off``:
-
- .. sourcecode:: apache
-
- WSGIRestrictStdout Off
-
- Alternatively you can also replace the standard out in the .wsgi file
- with a different stream::
-
- import sys
- sys.stdout = sys.stderr
-
-**Problem:** accessing resources gives IO errors
- Your application probably is a single .py file you symlinked into
- the site-packages folder. Please be aware that this does not work,
- instead you either have to put the folder into the pythonpath the
- file is stored in, or convert your application into a package.
-
- The reason for this is that for non-installed packages, the module
- filename is used to locate the resources and for symlinks the wrong
- filename is picked up.
-
-Support for Automatic Reloading
--------------------------------
-
-To help deployment tools you can activate support for automatic
-reloading. Whenever something changes the ``.wsgi`` file, `mod_wsgi` will
-reload all the daemon processes for us.
-
-For that, just add the following directive to your `Directory` section:
-
-.. sourcecode:: apache
-
- WSGIScriptReloading On
-
-Working with Virtual Environments
----------------------------------
-
-Virtual environments have the advantage that they never install the
-required dependencies system wide so you have a better control over what
-is used where. If you want to use a virtual environment with mod_wsgi
-you have to modify your ``.wsgi`` file slightly.
-
-Add the following lines to the top of your ``.wsgi`` file::
-
- activate_this = '/path/to/env/bin/activate_this.py'
- with open(activate_this) as file_:
- exec(file_.read(), dict(__file__=activate_this))
-
-This sets up the load paths according to the settings of the virtual
-environment. Keep in mind that the path has to be absolute.
+ $ sudo /home/hello/venv/bin/mod_wsgi-express start-server \
+ /home/hello/wsgi.py \
+ --user hello --group hello --port 80 --processes 4
diff --git a/docs/deploying/nginx.rst b/docs/deploying/nginx.rst
new file mode 100644
index 00000000..6b25c073
--- /dev/null
+++ b/docs/deploying/nginx.rst
@@ -0,0 +1,69 @@
+nginx
+=====
+
+`nginx`_ is a fast, production level HTTP server. When serving your
+application with one of the WSGI servers listed in :doc:`index`, it is
+often good or necessary to put a dedicated HTTP server in front of it.
+This "reverse proxy" can handle incoming requests, TLS, and other
+security and performance concerns better than the WSGI server.
+
+Nginx can be installed using your system package manager, or a pre-built
+executable for Windows. Installing and running Nginx itself is outside
+the scope of this doc. This page outlines the basics of configuring
+Nginx to proxy your application. Be sure to read its documentation to
+understand what features are available.
+
+.. _nginx: https://nginx.org/
+
+
+Domain Name
+-----------
+
+Acquiring and configuring a domain name is outside the scope of this
+doc. In general, you will buy a domain name from a registrar, pay for
+server space with a hosting provider, and then point your registrar
+at the hosting provider's name servers.
+
+To simulate this, you can also edit your ``hosts`` file, located at
+``/etc/hosts`` on Linux. Add a line that associates a name with the
+local IP.
+
+Modern Linux systems may be configured to treat any domain name that
+ends with ``.localhost`` like this without adding it to the ``hosts``
+file.
+
+.. code-block:: python
+ :caption: ``/etc/hosts``
+
+ 127.0.0.1 hello.localhost
+
+
+Configuration
+-------------
+
+The nginx configuration is located at ``/etc/nginx/nginx.conf`` on
+Linux. It may be different depending on your operating system. Check the
+docs and look for ``nginx.conf``.
+
+Remove or comment out any existing ``server`` section. Add a ``server``
+section and use the ``proxy_pass`` directive to point to the address the
+WSGI server is listening on. We'll assume the WSGI server is listening
+locally at ``http://127.0.0.1:8000``.
+
+.. code-block:: nginx
+ :caption: ``/etc/nginx.conf``
+
+ server {
+ listen 80;
+ server_name _;
+
+ location / {
+ proxy_pass http://127.0.0.1:8000/;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Prefix /;
+ }
+ }
+
+Then :doc:`proxy_fix` so that your application uses these headers.
diff --git a/docs/deploying/proxy_fix.rst b/docs/deploying/proxy_fix.rst
new file mode 100644
index 00000000..e2c42e82
--- /dev/null
+++ b/docs/deploying/proxy_fix.rst
@@ -0,0 +1,33 @@
+Tell Flask it is Behind a Proxy
+===============================
+
+When using a reverse proxy, or many Python hosting platforms, the proxy
+will intercept and forward all external requests to the local WSGI
+server.
+
+From the WSGI server and Flask application's perspectives, requests are
+now coming from the HTTP server to the local address, rather than from
+the remote address to the external server address.
+
+HTTP servers should set ``X-Forwarded-`` headers to pass on the real
+values to the application. The application can then be told to trust and
+use those values by wrapping it with the
+:doc:`werkzeug:middleware/proxy_fix` middleware provided by Werkzeug.
+
+This middleware should only be used if the application is actually
+behind a proxy, and should be configured with the number of proxies that
+are chained in front of it. Not all proxies set all the headers. Since
+incoming headers can be faked, you must set how many proxies are setting
+each header so the middleware knows what to trust.
+
+.. code-block:: python
+
+ from werkzeug.middleware.proxy_fix import ProxyFix
+
+ app.wsgi_app = ProxyFix(
+ app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
+ )
+
+Remember, only apply this middleware if you are behind a proxy, and set
+the correct number of proxies that set each header. It can be a security
+issue if you get this configuration wrong.
diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst
index b6958dc0..2da5efe2 100644
--- a/docs/deploying/uwsgi.rst
+++ b/docs/deploying/uwsgi.rst
@@ -1,71 +1,145 @@
uWSGI
=====
-uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
-`cherokee`_; see :doc:`fastcgi` and :doc:`wsgi-standalone` for other options.
-To use your WSGI application with uWSGI protocol you will need a uWSGI server
-first. uWSGI is both a protocol and an application server; the application
-server can serve uWSGI, FastCGI, and HTTP protocols.
+`uWSGI`_ is a fast, compiled server suite with extensive configuration
+and capabilities beyond a basic server.
-The most popular uWSGI server is `uwsgi`_, which we will use for this
-guide. Make sure to have it installed to follow along.
+* It can be very performant due to being a compiled program.
+* It is complex to configure beyond the basic application, and has so
+ many options that it can be difficult for beginners to understand.
+* It does not support Windows (but does run on WSL).
+* It requires a compiler to install in some cases.
-.. admonition:: Watch Out
+This page outlines the basics of running uWSGI. Be sure to read its
+documentation to understand what features are available.
- Please make sure in advance that any ``app.run()`` calls you might
- have in your application file are inside an ``if __name__ ==
- '__main__':`` block or moved to a separate file. Just make sure it's
- not called because this will always start a local WSGI server which
- we do not want if we deploy that application to uWSGI.
+.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
-Starting your app with uwsgi
-----------------------------
-`uwsgi` is designed to operate on WSGI callables found in python modules.
+Installing
+----------
-Given a flask application in myapp.py, use the following command:
+uWSGI has multiple ways to install it. The most straightforward is to
+install the ``pyuwsgi`` package, which provides precompiled wheels for
+common platforms. However, it does not provide SSL support, which can be
+provided with a reverse proxy instead.
-.. sourcecode:: text
+Create a virtualenv, install your application, then install ``pyuwsgi``.
- $ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app
+.. code-block:: text
-The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME``
-to uwsgi, since it is smarter about that.
-It is used together with the ``--mount`` directive which will make
-requests to ``/yourapplication`` be directed to ``myapp:app``.
-If your application is accessible at root level, you can use a
-single ``/`` instead of ``/yourapplication``. ``myapp`` refers to the name of
-the file of your flask application (without extension) or the module which
-provides ``app``. ``app`` is the callable inside of your application (usually
-the line reads ``app = Flask(__name__)``).
+ $ cd hello-app
+ $ python -m venv venv
+ $ . venv/bin/activate
+ $ pip install . # install your application
+ $ pip install pyuwsgi
-If you want to deploy your flask application inside of a virtual environment,
-you need to also add ``--virtualenv /path/to/virtual/environment``. You might
-also need to add ``--plugin python`` or ``--plugin python3`` depending on which
-python version you use for your project.
+If you have a compiler available, you can install the ``uwsgi`` package
+instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
+Either method will include SSL support.
-Configuring nginx
+.. code-block:: text
+
+ $ pip install uwsgi
+
+ # or
+ $ pip install --no-binary pyuwsgi pyuwsgi
+
+
+Running
+-------
+
+The most basic way to run uWSGI is to tell it to start an HTTP server
+and import your application.
+
+.. code-block:: text
+
+ $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app
+
+ *** Starting uWSGI 2.0.20 (64bit) on [x] ***
+ *** Operational MODE: preforking ***
+ mounting hello:app on /
+ spawned uWSGI master process (pid: x)
+ spawned uWSGI worker 1 (pid: x, cores: 1)
+ spawned uWSGI worker 2 (pid: x, cores: 1)
+ spawned uWSGI worker 3 (pid: x, cores: 1)
+ spawned uWSGI worker 4 (pid: x, cores: 1)
+ spawned uWSGI http 1 (pid: x)
+
+If you're using the app factory pattern, you'll need to create a small
+Python file to create the app, then point uWSGI at that.
+
+.. code-block:: python
+ :caption: ``wsgi.py``
+
+ from hello import create_app
+
+ app = create_app()
+
+.. code-block:: text
+
+ $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app
+
+The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The
+``--master`` option specifies the standard worker manager. The ``-p``
+option starts 4 worker processes; a starting value could be ``CPU * 2``.
+The ``-w`` option tells uWSGI how to import your application
+
+
+Binding Externally
+------------------
+
+uWSGI should not be run as root with the configuration shown in this doc
+because it would cause your application code to run as root, which is
+not secure. However, this means it will not be possible to bind to port
+80 or 443. Instead, a reverse proxy such as :doc:`nginx` or
+:doc:`apache-httpd` should be used in front of uWSGI. It is possible to
+run uWSGI as root securely, but that is beyond the scope of this doc.
+
+uWSGI has optimized integration with `Nginx uWSGI`_ and
+`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using
+a standard HTTP proxy. That configuration is beyond the scope of this
+doc, see the links for more information.
+
+.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
+.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
+
+You can bind to all external IPs on a non-privileged port using the
+``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy
+setup, otherwise it will be possible to bypass the proxy.
+
+.. code-block:: text
+
+ $ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app
+
+``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
+IP address in your browser.
+
+
+Async with gevent
-----------------
-A basic flask nginx configuration looks like this::
+The default sync worker is appropriate for many use cases. If you need
+asynchronous support, uWSGI provides a `gevent`_ worker. This is not the
+same as Python's ``async/await``, or the ASGI server spec. You must
+actually use gevent in your own code to see any benefit to using the
+worker.
- location = /yourapplication { rewrite ^ /yourapplication/; }
- location /yourapplication { try_files $uri @yourapplication; }
- location @yourapplication {
- include uwsgi_params;
- uwsgi_pass unix:/tmp/yourapplication.sock;
- }
+When using gevent, greenlet>=1.0 is required, otherwise context locals
+such as ``request`` will not work as expected. When using PyPy,
+PyPy>=7.3.7 is required.
-This configuration binds the application to ``/yourapplication``. If you want
-to have it in the URL root its a bit simpler::
+.. code-block:: text
- location / { try_files $uri @yourapplication; }
- location @yourapplication {
- include uwsgi_params;
- uwsgi_pass unix:/tmp/yourapplication.sock;
- }
+ $ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app
-.. _nginx: https://nginx.org/
-.. _lighttpd: https://www.lighttpd.net/
-.. _cherokee: https://cherokee-project.com/
-.. _uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/
+ *** Starting uWSGI 2.0.20 (64bit) on [x] ***
+ *** Operational MODE: async ***
+ mounting hello:app on /
+ spawned uWSGI master process (pid: x)
+ spawned uWSGI worker 1 (pid: x, cores: 100)
+ spawned uWSGI http 1 (pid: x)
+ *** running gevent loop engine [addr:x] ***
+
+
+.. _gevent: https://www.gevent.org/
diff --git a/docs/deploying/waitress.rst b/docs/deploying/waitress.rst
new file mode 100644
index 00000000..9b2fe13f
--- /dev/null
+++ b/docs/deploying/waitress.rst
@@ -0,0 +1,75 @@
+Waitress
+========
+
+`Waitress`_ is a pure Python WSGI server.
+
+* It is easy to configure.
+* It supports Windows directly.
+* It is easy to install as it does not require additional dependencies
+ or compilation.
+* It does not support streaming requests, full request data is always
+ buffered.
+* It uses a single process with multiple thread workers.
+
+This page outlines the basics of running Waitress. Be sure to read its
+documentation and ``waitress-serve --help`` to understand what features
+are available.
+
+.. _Waitress: https://docs.pylonsproject.org/projects/waitress/
+
+
+Installing
+----------
+
+Create a virtualenv, install your application, then install
+``waitress``.
+
+.. code-block:: text
+
+ $ cd hello-app
+ $ python -m venv venv
+ $ . venv/bin/activate
+ $ pip install . # install your application
+ $ pip install waitress
+
+
+Running
+-------
+
+The only required argument to ``waitress-serve`` tells it how to load
+your Flask application. The syntax is ``{module}:{app}``. ``module`` is
+the dotted import name to the module with your application. ``app`` is
+the variable with the application. If you're using the app factory
+pattern, use ``--call {module}:{factory}`` instead.
+
+.. code-block:: text
+
+ # equivalent to 'from hello import app'
+ $ waitress-serve hello:app --host 127.0.0.1
+
+ # equivalent to 'from hello import create_app; create_app()'
+ $ waitress-serve --call hello:create_app --host 127.0.0.1
+
+ Serving on http://127.0.0.1:8080
+
+The ``--host`` option binds the server to local ``127.0.0.1`` only.
+
+Logs for each request aren't shown, only errors are shown. Logging can
+be configured through the Python interface instead of the command line.
+
+
+Binding Externally
+------------------
+
+Waitress should not be run as root because it would cause your
+application code to run as root, which is not secure. However, this
+means it will not be possible to bind to port 80 or 443. Instead, a
+reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
+in front of Waitress.
+
+You can bind to all external IPs on a non-privileged port by not
+specifying the ``--host`` option. Don't do this when using a revers
+proxy setup, otherwise it will be possible to bypass the proxy.
+
+``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
+IP address in your browser.
diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst
deleted file mode 100644
index e66eacba..00000000
--- a/docs/deploying/wsgi-standalone.rst
+++ /dev/null
@@ -1,262 +0,0 @@
-Standalone WSGI Servers
-=======================
-
-Most WSGI servers also provide HTTP servers, so they can run a WSGI
-application and make it available externally.
-
-It may still be a good idea to run the server behind a dedicated HTTP
-server such as Apache or Nginx. See :ref:`deploying-proxy-setups` if you
-run into issues with that.
-
-
-Gunicorn
---------
-
-`Gunicorn`_ is a WSGI and HTTP server for UNIX. To run a Flask
-application, tell Gunicorn how to import your Flask app object.
-
-.. code-block:: text
-
- $ gunicorn -w 4 -b 0.0.0.0:5000 your_project:app
-
-The ``-w 4`` option uses 4 workers to handle 4 requests at once. The
-``-b 0.0.0.0:5000`` serves the application on all interfaces on port
-5000.
-
-Gunicorn provides many options for configuring the server, either
-through a configuration file or with command line options. Use
-``gunicorn --help`` or see the docs for more information.
-
-The command expects the name of your module or package to import and
-the application instance within the module. If you use the application
-factory pattern, you can pass a call to that.
-
-.. code-block:: text
-
- $ gunicorn -w 4 -b 0.0.0.0:5000 "myproject:create_app()"
-
-
-Async with Gevent or Eventlet
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The default sync worker is appropriate for many use cases. If you need
-asynchronous support, Gunicorn provides workers using either `gevent`_
-or `eventlet`_. This is not the same as Python's ``async/await``, or the
-ASGI server spec.
-
-When using either gevent or eventlet, greenlet>=1.0 is required,
-otherwise context locals such as ``request`` will not work as expected.
-When using PyPy, PyPy>=7.3.7 is required.
-
-To use gevent:
-
-.. code-block:: text
-
- $ gunicorn -k gevent -b 0.0.0.0:5000 your_project:app
-
-To use eventlet:
-
-.. code-block:: text
-
- $ gunicorn -k eventlet -b 0.0.0.0:5000 your_project:app
-
-
-.. _Gunicorn: https://gunicorn.org/
-.. _gevent: http://www.gevent.org/
-.. _eventlet: https://eventlet.net/
-.. _greenlet: https://greenlet.readthedocs.io/en/latest/
-
-
-uWSGI
------
-
-`uWSGI`_ is a fast application server written in C. It is very
-configurable, which makes it more complicated to setup than Gunicorn.
-It also provides many other utilities for writing robust web
-applications. To run a Flask application, tell uWSGI how to import
-your Flask app object.
-
-.. code-block:: text
-
- $ uwsgi --master -p 4 --http 0.0.0.0:5000 -w your_project:app
-
-The ``-p 4`` option uses 4 workers to handle 4 requests at once. The
-``--http 0.0.0.0:5000`` serves the application on all interfaces on port
-5000.
-
-uWSGI has optimized integration with Nginx and Apache instead of using
-a standard HTTP proxy. See :doc:`configuring uWSGI and Nginx `.
-
-
-Async with Gevent
-~~~~~~~~~~~~~~~~~
-
-The default sync worker is appropriate for many use cases. If you need
-asynchronous support, uWSGI provides workers using `gevent`_. It also
-supports other async modes, see the docs for more information. This is
-not the same as Python's ``async/await``, or the ASGI server spec.
-
-When using gevent, greenlet>=1.0 is required, otherwise context locals
-such as ``request`` will not work as expected. When using PyPy,
-PyPy>=7.3.7 is required.
-
-.. code-block:: text
-
- $ uwsgi --master --gevent 100 --http 0.0.0.0:5000 -w your_project:app
-
-.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
-
-
-Gevent
-------
-
-Prefer using `Gunicorn`_ with Gevent workers rather than using Gevent
-directly. Gunicorn provides a much more configurable and
-production-tested server. See the section on Gunicorn above.
-
-`Gevent`_ allows writing asynchronous, coroutine-based code that looks
-like standard synchronous Python. It uses `greenlet`_ to enable task
-switching without writing ``async/await`` or using ``asyncio``.
-
-It provides a WSGI server that can handle many connections at once
-instead of one per worker process.
-
-`Eventlet`_, described below, is another library that does the same
-thing. Certain dependencies you have, or other consideration, may affect
-which of the two you choose to use
-
-To use gevent to serve your application, import its ``WSGIServer`` and
-use it to run your ``app``.
-
-.. code-block:: python
-
- from gevent.pywsgi import WSGIServer
- from your_project import app
-
- http_server = WSGIServer(("", 5000), app)
- http_server.serve_forever()
-
-
-Eventlet
---------
-
-Prefer using `Gunicorn`_ with Eventlet workers rather than using
-Eventlet directly. Gunicorn provides a much more configurable and
-production-tested server. See the section on Gunicorn above.
-
-`Eventlet`_ allows writing asynchronous, coroutine-based code that looks
-like standard synchronous Python. It uses `greenlet`_ to enable task
-switching without writing ``async/await`` or using ``asyncio``.
-
-It provides a WSGI server that can handle many connections at once
-instead of one per worker process.
-
-`Gevent`_, described above, is another library that does the same
-thing. Certain dependencies you have, or other consideration, may affect
-which of the two you choose to use
-
-To use eventlet to serve your application, import its ``wsgi.server``
-and use it to run your ``app``.
-
-.. code-block:: python
-
- import eventlet
- from eventlet import wsgi
- from your_project import app
-
- wsgi.server(eventlet.listen(("", 5000), app)
-
-
-Twisted Web
------------
-
-`Twisted Web`_ is the web server shipped with `Twisted`_, a mature,
-non-blocking event-driven networking library. Twisted Web comes with a
-standard WSGI container which can be controlled from the command line using
-the ``twistd`` utility:
-
-.. code-block:: text
-
- $ twistd web --wsgi myproject.app
-
-This example will run a Flask application called ``app`` from a module named
-``myproject``.
-
-Twisted Web supports many flags and options, and the ``twistd`` utility does
-as well; see ``twistd -h`` and ``twistd web -h`` for more information. For
-example, to run a Twisted Web server in the foreground, on port 8080, with an
-application from ``myproject``:
-
-.. code-block:: text
-
- $ twistd -n web --port tcp:8080 --wsgi myproject.app
-
-.. _Twisted: https://twistedmatrix.com/trac/
-.. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb
-
-
-.. _deploying-proxy-setups:
-
-Proxy Setups
-------------
-
-If you deploy your application using one of these servers behind an HTTP proxy
-you will need to rewrite a few headers in order for the application to work.
-The two problematic values in the WSGI environment usually are ``REMOTE_ADDR``
-and ``HTTP_HOST``. You can configure your httpd to pass these headers, or you
-can fix them in middleware. Werkzeug ships a fixer that will solve some common
-setups, but you might want to write your own WSGI middleware for specific
-setups.
-
-Here's a simple nginx configuration which proxies to an application served on
-localhost at port 8000, setting appropriate headers:
-
-.. sourcecode:: nginx
-
- server {
- listen 80;
-
- server_name _;
-
- access_log /var/log/nginx/access.log;
- error_log /var/log/nginx/error.log;
-
- location / {
- proxy_pass http://127.0.0.1:8000/;
- proxy_redirect off;
-
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
- }
-
-If your httpd is not providing these headers, the most common setup invokes the
-host being set from ``X-Forwarded-Host`` and the remote address from
-``X-Forwarded-For``::
-
- from werkzeug.middleware.proxy_fix import ProxyFix
- app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
-
-.. admonition:: Trusting Headers
-
- Please keep in mind that it is a security issue to use such a middleware in
- a non-proxy setup because it will blindly trust the incoming headers which
- might be forged by malicious clients.
-
-If you want to rewrite the headers from another header, you might want to
-use a fixer like this::
-
- class CustomProxyFix(object):
-
- def __init__(self, app):
- self.app = app
-
- def __call__(self, environ, start_response):
- host = environ.get('HTTP_X_FHOST', '')
- if host:
- environ['HTTP_HOST'] = host
- return self.app(environ, start_response)
-
- app.wsgi_app = CustomProxyFix(app.wsgi_app)