diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst
index 89eb4e3710..cf7df1dae3 100644
--- a/doc/source/cli/command-objects/server.rst
+++ b/doc/source/cli/command-objects/server.rst
@@ -10,6 +10,9 @@ Compute v2
 .. autoprogram-cliff:: openstack.compute.v2
    :command: server create
 
+.. autoprogram-cliff:: openstack.compute.v2
+   :command: server evacuate
+
 .. autoprogram-cliff:: openstack.compute.v2
    :command: server delete
 
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 294b468331..7f1bc08852 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -2502,6 +2502,118 @@ class RebuildServer(command.ShowOne):
         return zip(*sorted(details.items()))
 
 
+class EvacuateServer(command.ShowOne):
+    _description = _("""Evacuate a server to a different host.
+
+This command is used to recreate a server after the host it was on has failed.
+It can only be used if the compute service that manages the server is down.
+This command should only be used by an admin after they have confirmed that the
+instance is not running on the failed host.
+
+If the server instance was created with an ephemeral root disk on non-shared
+storage the server will be rebuilt using the original glance image preserving
+the ports and any attached data volumes.
+
+If the server uses boot for volume or has its root disk on shared storage the
+root disk will be preserved and reused for the evacuated instance on the new
+host.""")
+
+    def get_parser(self, prog_name):
+        parser = super(EvacuateServer, self).get_parser(prog_name)
+        parser.add_argument(
+            'server',
+            metavar='<server>',
+            help=_('Server (name or ID)'),
+        )
+
+        parser.add_argument(
+            '--wait', action='store_true',
+            help=_('Wait for evacuation to complete'),
+        )
+        parser.add_argument(
+            '--host', metavar='<host>', default=None,
+            help=_(
+                'Set the preferred host on which to rebuild the evacuated '
+                'server. The host will be validated by the scheduler. '
+                '(supported by --os-compute-api-version 2.29 or above)'
+            ),
+        )
+        shared_storage_group = parser.add_mutually_exclusive_group()
+        shared_storage_group.add_argument(
+            '--password', metavar='<password>', default=None,
+            help=_(
+                'Set the password on the evacuated instance. This option is '
+                'mutually exclusive with the --shared-storage option'
+            ),
+        )
+        shared_storage_group.add_argument(
+            '--shared-storage', action='store_true', dest='shared_storage',
+            help=_(
+                'Indicate that the instance is on shared storage. '
+                'This will be auto-calculated with '
+                '--os-compute-api-version 2.14 and greater and should not '
+                'be used with later microversions. This option is mutually '
+                'exclusive with the --password option'
+            ),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+
+        def _show_progress(progress):
+            if progress:
+                self.app.stdout.write('\rProgress: %s' % progress)
+                self.app.stdout.flush()
+
+        compute_client = self.app.client_manager.compute
+        image_client = self.app.client_manager.image
+
+        if parsed_args.host:
+            if compute_client.api_version < api_versions.APIVersion('2.29'):
+                msg = _(
+                    '--os-compute-api-version 2.29 or later is required '
+                    'to specify a preferred host.'
+                )
+                raise exceptions.CommandError(msg)
+
+        if parsed_args.shared_storage:
+            if compute_client.api_version > api_versions.APIVersion('2.13'):
+                msg = _(
+                    '--os-compute-api-version 2.13 or earlier is required '
+                    'to specify shared-storage.'
+                )
+                raise exceptions.CommandError(msg)
+
+        kwargs = {
+            'host': parsed_args.host,
+            'password': parsed_args.password,
+        }
+
+        if compute_client.api_version <= api_versions.APIVersion('2.13'):
+            kwargs['on_shared_storage'] = parsed_args.shared_storage
+
+        server = utils.find_resource(
+            compute_client.servers, parsed_args.server)
+
+        server = server.evacuate(**kwargs)
+
+        if parsed_args.wait:
+            if utils.wait_for_status(
+                compute_client.servers.get,
+                server.id,
+                callback=_show_progress,
+            ):
+                self.app.stdout.write(_('Complete\n'))
+            else:
+                LOG.error(_('Error evacuating server: %s'), server.id)
+                self.app.stdout.write(_('Error evacuating server\n'))
+                raise SystemExit
+
+        details = _prep_server_detail(
+            compute_client, image_client, server, refresh=False)
+        return zip(*sorted(details.items()))
+
+
 class RemoveFixedIP(command.Command):
     _description = _("Remove fixed IP address from server")
 
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 380ef66b4e..20e3f70e95 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -4982,6 +4982,167 @@ class TestServerRebuild(TestServer):
                           self.cmd, arglist, verifylist)
 
 
+class TestEvacuateServer(TestServer):
+
+    def setUp(self):
+        super(TestEvacuateServer, self).setUp()
+        # Return value for utils.find_resource for image
+        self.image = image_fakes.FakeImage.create_one_image()
+        self.images_mock.get.return_value = self.image
+
+        # Fake the rebuilt new server.
+        attrs = {
+            'image': {
+                'id': self.image.id
+            },
+            'networks': {},
+            'adminPass': 'passw0rd',
+        }
+        new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs)
+
+        # Fake the server to be rebuilt. The IDs of them should be the same.
+        attrs['id'] = new_server.id
+        methods = {
+            'evacuate': new_server,
+        }
+        self.server = compute_fakes.FakeServer.create_one_server(
+            attrs=attrs,
+            methods=methods
+        )
+
+        # Return value for utils.find_resource for server.
+        self.servers_mock.get.return_value = self.server
+
+        self.cmd = server.EvacuateServer(self.app, None)
+
+    def _test_evacuate(self, args, verify_args, evac_args):
+        parsed_args = self.check_parser(self.cmd, args, verify_args)
+
+        # Get the command object to test
+        self.cmd.take_action(parsed_args)
+
+        self.servers_mock.get.assert_called_with(self.server.id)
+        self.server.evacuate.assert_called_with(**evac_args)
+
+    def test_evacuate(self):
+        args = [
+            self.server.id,
+        ]
+        verify_args = [
+            ('server', self.server.id),
+        ]
+        evac_args = {
+            'host': None, 'on_shared_storage': False, 'password': None,
+        }
+        self._test_evacuate(args, verify_args, evac_args)
+
+    def test_evacuate_with_password(self):
+        args = [
+            self.server.id,
+            '--password', 'password',
+        ]
+        verify_args = [
+            ('server', self.server.id),
+            ('password', 'password'),
+        ]
+        evac_args = {
+            'host': None, 'on_shared_storage': False, 'password': 'password',
+        }
+        self._test_evacuate(args, verify_args, evac_args)
+
+    def test_evacuate_with_host(self):
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.29')
+
+        host = 'target-host'
+        args = [
+            self.server.id,
+            '--host', 'target-host',
+        ]
+        verify_args = [
+            ('server', self.server.id),
+            ('host', 'target-host'),
+        ]
+        evac_args = {'host': host, 'password': None}
+
+        self._test_evacuate(args, verify_args, evac_args)
+
+    def test_evacuate_with_host_pre_v229(self):
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.28')
+
+        args = [
+            self.server.id,
+            '--host', 'target-host',
+        ]
+        verify_args = [
+            ('server', self.server.id),
+            ('host', 'target-host'),
+        ]
+        parsed_args = self.check_parser(self.cmd, args, verify_args)
+
+        self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+
+    def test_evacuate_without_share_storage(self):
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.13')
+
+        args = [
+            self.server.id,
+            '--shared-storage'
+        ]
+        verify_args = [
+            ('server', self.server.id),
+            ('shared_storage', True),
+        ]
+        evac_args = {
+            'host': None, 'on_shared_storage': True, 'password': None,
+        }
+        self._test_evacuate(args, verify_args, evac_args)
+
+    def test_evacuate_without_share_storage_post_v213(self):
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.14')
+
+        args = [
+            self.server.id,
+            '--shared-storage'
+        ]
+        verify_args = [
+            ('server', self.server.id),
+            ('shared_storage', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, args, verify_args)
+
+        self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+
+    @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
+    def test_evacuate_with_wait_ok(self, mock_wait_for_status):
+        args = [
+            self.server.id,
+            '--wait',
+        ]
+        verify_args = [
+            ('server', self.server.id),
+            ('wait', True),
+        ]
+        evac_args = {
+            'host': None, 'on_shared_storage': False, 'password': None,
+        }
+        self._test_evacuate(args, verify_args, evac_args)
+        mock_wait_for_status.assert_called_once_with(
+            self.servers_mock.get,
+            self.server.id,
+            callback=mock.ANY,
+        )
+
+
 class TestServerRemoveFixedIP(TestServer):
 
     def setUp(self):
diff --git a/releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml b/releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml
new file mode 100644
index 0000000000..41bbed736a
--- /dev/null
+++ b/releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add ``server evacuate`` command. This command will recreate an instance
+    from scratch on a new host and is intended to be used when the original
+    host fails.
diff --git a/setup.cfg b/setup.cfg
index 8363ec6cfd..a29852e353 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -103,6 +103,7 @@ openstack.compute.v2 =
     server_create = openstackclient.compute.v2.server:CreateServer
     server_delete = openstackclient.compute.v2.server:DeleteServer
     server_dump_create = openstackclient.compute.v2.server:CreateServerDump
+    server_evacuate = openstackclient.compute.v2.server:EvacuateServer
     server_list = openstackclient.compute.v2.server:ListServer
     server_lock = openstackclient.compute.v2.server:LockServer
     server_migrate = openstackclient.compute.v2.server:MigrateServer