diff --git a/doc/requirements.txt b/doc/requirements.txt
index eb249f34b1..83f6935eda 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -6,6 +6,7 @@ os-api-ref>=1.4.0 # Apache-2.0
 openstackdocstheme>=1.18.1 # Apache-2.0
 reno>=2.5.0 # Apache-2.0
 sphinxcontrib-apidoc>=0.2.0  # BSD
+whereto>=0.3.0  # Apache-2.0
 
 # needed for apidoc support
 xattr>=0.9.2
diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess
new file mode 100644
index 0000000000..15c2755e1b
--- /dev/null
+++ b/doc/source/_extra/.htaccess
@@ -0,0 +1,45 @@
+# Documentation redirects
+#
+# NOTE(rosmaita): the web server is already doing a rewrite of the
+# pre-pike-url-format ^/developer/glance/(.*) to /glance/latest/$1
+# so the only URLs we will see should be of the form /glance/release/whatever
+
+# Redirects for the new directory structure introduced by commit
+# 1c7f556d4f77d2dd7f282f2b41bdfb2abb6e5740
+# to: admin
+RedirectMatch 301 ^/glance/([^/]+)/apache-httpd.html$ /glance/$1/admin/apache-httpd.html
+RedirectMatch 301 ^/glance/([^/]+)/authentication.html$ /glance/$1/admin/authentication.html
+RedirectMatch 301 ^/glance/([^/]+)/cache.html$ /glance/$1/admin/cache.html
+RedirectMatch 301 ^/glance/([^/]+)/controllingservers.html$ /glance/$1/admin/controllingservers.html
+RedirectMatch 301 ^/glance/([^/]+)/db-sqlalchemy-migrate.html$ /glance/$1/admin/db-sqlalchemy-migrate.html
+RedirectMatch 301 ^/glance/([^/]+)/db.html$ /glance/$1/admin/db.html
+RedirectMatch 301 ^/glance/([^/]+)/flows.html$ /glance/$1/admin/flows.html
+RedirectMatch 301 ^/glance/([^/]+)/notifications.html$ /glance/$1/admin/notifications.html
+RedirectMatch 301 ^/glance/([^/]+)/policies.html$ /glance/$1/admin/policies.html
+RedirectMatch 301 ^/glance/([^/]+)/property-protections.html$ /glance/$1/admin/property-protections.html
+RedirectMatch 301 ^/glance/([^/]+)/requirements.html$ /glance/$1/admin/requirements.html
+RedirectMatch 301 ^/glance/([^/]+)/rollingupgrades.html$ /glance/$1/admin/rollingupgrades.html
+RedirectMatch 301 ^/glance/([^/]+)/tasks.html$ /glance/$1/admin/tasks.html
+# to: configuration
+RedirectMatch 301 ^/glance/([^/]+)/configuring.html$ /glance/$1/configuration/configuring.html
+RedirectMatch 301 ^/glance/([^/]+)/sample-configuration.html$ /glance/$1/configuration/sample-configuration.html
+RedirectMatch 301 ^/glance/([^/]+)/opts/(.*) /glance/$1/configuration/$2
+# to: contributor
+RedirectMatch 301 ^/glance/([^/]+)/architecture.html$ /glance/$1/contributor/architecture.html
+RedirectMatch 301 ^/glance/([^/]+)/database_architecture.html$ /glance/$1/contributor/database_architecture.html
+RedirectMatch 301 ^/glance/([^/]+)/database_migrations.html$ /glance/$1/contributor/database_migrations.html
+RedirectMatch 301 ^/glance/([^/]+)/domain_implementation.html$ /glance/$1/contributor/domain_implementation.html
+RedirectMatch 301 ^/glance/([^/]+)/domain_model.html$ /glance/$1/contributor/domain_model.html
+RedirectMatch 301 ^/glance/([^/]+)/contributing/(.*) /glance/$1/contributor/$2
+# to: user
+RedirectMatch 301 ^/glance/([^/]+)/common-image-properties.html$ /glance/$1/user/common-image-properties.html
+RedirectMatch 301 ^/glance/([^/]+)/formats.html$ /glance/$1/user/formats.html
+RedirectMatch 301 ^/glance/([^/]+)/glanceapi.html$ /glance/$1/user/glanceapi.html
+RedirectMatch 301 ^/glance/([^/]+)/glanceclient.html$ /glance/$1/user/glanceclient.html
+RedirectMatch 301 ^/glance/([^/]+)/glancemetadefcatalogapi.html$ /glance/$1/user/glancemetadefcatalogapi.html
+RedirectMatch 301 ^/glance/([^/]+)/identifiers.html$ /glance/$1/user/identifiers.html
+RedirectMatch 301 ^/glance/([^/]+)/metadefs-concepts.html$ /glance/$1/user/metadefs-concepts.html
+RedirectMatch 301 ^/glance/([^/]+)/signature.html$ /glance/$1/user/signature.html
+RedirectMatch 301 ^/glance/([^/]+)/statuses.html$ /glance/$1/user/statuses.html
+# to: cli
+RedirectMatch 301 ^/glance/([^/]+)/man/(.*) /glance/$1/cli/$2
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 27f8bfa014..355cc9c88c 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -248,6 +248,10 @@ except Exception:
 # template names.
 #html_additional_pages = {}
 
+# Add any paths that contain "extra" files, such as .htaccess or
+# robots.txt.
+html_extra_path = ['_extra']
+
 # If false, no module index is generated.
 html_use_modindex = True
 
diff --git a/doc/test/redirect-tests.txt b/doc/test/redirect-tests.txt
new file mode 100644
index 0000000000..c0697bcd25
--- /dev/null
+++ b/doc/test/redirect-tests.txt
@@ -0,0 +1,61 @@
+# This file contains tests for redirects to handle existing URLs for docs that
+# have been moved. See https://docs.openstack.org/whereto/latest/ for details.
+
+### test files moved by commit 1c7f556d4f77d2dd7f282f2b41bdfb2abb6e5740
+# to: admin
+/glance/austin/apache-httpd.html 301 /glance/austin/admin/apache-httpd.html
+/glance/bexar/authentication.html 301 /glance/bexar/admin/authentication.html
+/glance/cactus/cache.html 301 /glance/cactus/admin/cache.html
+/glance/diablo/controllingservers.html 301 /glance/diablo/admin/controllingservers.html
+/glance/essex/db-sqlalchemy-migrate.html 301 /glance/essex/admin/db-sqlalchemy-migrate.html
+/glance/folsom/db.html 301 /glance/folsom/admin/db.html
+/glance/grizzly/flows.html 301 /glance/grizzly/admin/flows.html
+/glance/havana/notifications.html 301 /glance/havana/admin/notifications.html
+/glance/icehouse/policies.html 301 /glance/icehouse/admin/policies.html
+/glance/juno/property-protections.html 301 /glance/juno/admin/property-protections.html
+/glance/kilo/requirements.html 301 /glance/kilo/admin/requirements.html
+/glance/liberty/rollingupgrades.html 301 /glance/liberty/admin/rollingupgrades.html
+/glance/mitaka/tasks.html 301 /glance/mitaka/admin/tasks.html
+# to: configuration
+/glance/newton/configuring.html 301 /glance/newton/configuration/configuring.html
+/glance/ocata/opts/glance_api.html 301 /glance/ocata/configuration/glance_api.html
+/glance/pike/opts/glance_cache.html 301 /glance/pike/configuration/glance_cache.html
+/glance/queens/opts/glance_manage.html 301 /glance/queens/configuration/glance_manage.html
+/glance/rocky/opts/glance_registry.html 301 /glance/rocky/configuration/glance_registry.html
+/glance/stein/opts/glance_scrubber.html 301 /glance/stein/configuration/glance_scrubber.html
+/glance/thompson/opts/index.html 301 /glance/thompson/configuration/index.html
+/glance/uvula/sample-configuration.html 301 /glance/uvula/configuration/sample-configuration.html
+# to: contributor
+/glance/violin/architecture.html 301 /glance/violin/contributor/architecture.html
+/glance/watt/contributing/blueprints.html 301 /glance/watt/contributor/blueprints.html
+/glance/xylophone/database_architecture.html 301 /glance/xylophone/contributor/database_architecture.html
+/glance/yaml/database_migrations.html 301 /glance/yaml/contributor/database_migrations.html
+/glance/zero/contributing/documentation.html 301 /glance/zero/contributor/documentation.html
+/glance/latest/domain_implementation.html 301 /glance/latest/contributor/domain_implementation.html
+/glance/latest/domain_model.html 301 /glance/latest/contributor/domain_model.html
+/glance/latest/contributing/index.html 301 /glance/latest/contributor/index.html
+/glance/latest/contributing/minor-code-changes.html 301 /glance/latest/contributor/minor-code-changes.html
+/glance/latest/contributing/refreshing-configs.html 301 /glance/latest/contributor/refreshing-configs.html
+/glance/latest/contributing/release-cpl.html 301 /glance/latest/contributor/release-cpl.html
+# to: user
+/glance/latest/common-image-properties.html 301 /glance/latest/user/common-image-properties.html
+/glance/latest/formats.html 301 /glance/latest/user/formats.html
+/glance/latest/glanceapi.html 301 /glance/latest/user/glanceapi.html
+/glance/latest/glanceclient.html 301 /glance/latest/user/glanceclient.html
+/glance/latest/glancemetadefcatalogapi.html 301 /glance/latest/user/glancemetadefcatalogapi.html
+/glance/latest/identifiers.html 301 /glance/latest/user/identifiers.html
+/glance/latest/metadefs-concepts.html 301 /glance/latest/user/metadefs-concepts.html
+/glance/latest/signature.html 301 /glance/latest/user/signature.html
+/glance/latest/statuses.html 301 /glance/latest/user/statuses.html
+# to: cli
+/glance/latest/man/glanceapi.html 301 /glance/latest/cli/glanceapi.html
+/glance/latest/man/glancecachecleaner.html 301 /glance/latest/cli/glancecachecleaner.html
+/glance/latest/man/glancecachemanage.html 301 /glance/latest/cli/glancecachemanage.html
+/glance/latest/man/glancecacheprefetcher.html 301 /glance/latest/cli/glancecacheprefetcher.html
+/glance/latest/man/glancecachepruner.html 301 /glance/latest/cli/glancecachepruner.html
+/glance/latest/man/glancecontrol.html 301 /glance/latest/cli/glancecontrol.html
+/glance/latest/man/glancemanage.html 301 /glance/latest/cli/glancemanage.html
+/glance/latest/man/glanceregistry.html 301 /glance/latest/cli/glanceregistry.html
+/glance/latest/man/glancereplicator.html 301 /glance/latest/cli/glancereplicator.html
+/glance/latest/man/glancescrubber.html 301 /glance/latest/cli/glancescrubber.html
+### end: test files moved by commit 1c7f556d4f77d2dd7f282f2b41bdfb2abb6e5740
diff --git a/lower-constraints.txt b/lower-constraints.txt
index 9fb157d08d..706e4b2eb7 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -140,6 +140,7 @@ urllib3==1.22
 vine==1.1.4
 voluptuous==0.11.1
 WebOb==1.7.1
+whereto===0.3.0
 wrapt==1.10.11
 WSME==0.8.0
 xattr==0.9.2
diff --git a/tox.ini b/tox.ini
index c337e05df9..cfcbab4068 100644
--- a/tox.ini
+++ b/tox.ini
@@ -88,7 +88,7 @@ commands = bindep test
 usedevelop = False
 
 [doc8]
-ignore-path = .venv,.git,.tox,*glance/locale*,*lib/python*,glance.egg*,api-ref/build,doc/build,doc/source/contributor/api
+ignore-path = .venv,.git,.tox,*glance/locale*,*lib/python*,glance.egg*,api-ref/build,doc/build,doc/source/contributor/api,doc/test
 
 [flake8]
 # TODO(dmllr): Analyze or fix the warnings blacklisted below
@@ -110,6 +110,7 @@ deps =
 commands =
   rm -fr doc/build
   sphinx-build -W -b html doc/source doc/build/html
+  whereto doc/source/_extra/.htaccess doc/test/redirect-tests.txt
 
 [testenv:venv]
 commands = {posargs}