Index: src/wp-includes/default-filters.php
===================================================================
--- src/wp-includes/default-filters.php	(revision 39646)
+++ src/wp-includes/default-filters.php	(working copy)
@@ -331,6 +331,10 @@
 add_action( 'post_updated',       'wp_check_for_changed_slugs', 12, 3 );
 add_action( 'attachment_updated', 'wp_check_for_changed_slugs', 12, 3 );
 
+// Redirect Old Term Slugs
+add_action( 'template_redirect', 'wp_old_term_slug_redirect'              );
+add_action( 'edited_term',       'wp_check_for_changed_term_slugs', 10, 5 );
+
 // Nonce check for Post Previews
 add_action( 'init', '_show_post_preview' );
 
Index: src/wp-includes/query.php
===================================================================
--- src/wp-includes/query.php	(revision 39646)
+++ src/wp-includes/query.php	(working copy)
@@ -920,6 +920,62 @@
 }
 
 /**
+ * Redirect old term slugs to the correct permalink.
+ *
+ * Attempts to find the current slug from the past slugs for a term.
+ *
+ * @since 4.7.1
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ */
+function wp_old_term_slug_redirect() {
+	if( is_404() && '' !== get_query_var( 'taxonomy' ) && '' !== get_query_var( 'term' ) ) {
+		global $wpdb;
+
+		$term_id = get_term( array(
+			'taxonomy' => get_query_var( 'taxonomy' ),
+			'fields' => 'ids',
+			'number' => 1,
+			'hide_empty' => false,
+			'meta_query' => array(
+				array(
+					'key' => '_wp_old_slug',
+					'value' => get_query_var( 'term' )
+				)
+			)
+		) );
+
+		if ( is_wp_error( $term_id ) || empty( $term_id ) ){
+			return;
+		}
+
+		$link = get_term_link( $term_id[0], get_query_var( 'taxonomy' ) );
+
+		if ( isset( $GLOBALS['wp_query']->query_vars['paged'] ) && $GLOBALS['wp_query']->query_vars['paged'] > 1 ) {
+			$link = user_trailingslashit( trailingslashit( $link ) . 'page/' . $GLOBALS['wp_query']->query_vars['paged'] );
+		} elseif( is_embed() ) {
+			$link = user_trailingslashit( trailingslashit( $link ) . 'embed' );
+		}
+
+		/**
+		 * Filters the old term slug redirect URL.
+		 *
+		 * @since 4.7.1
+		 *
+		 * @param string $link The redirect URL.
+		 */
+		$link = apply_filters( 'old_term_slug_redirect_url', $link );
+
+		if ( ! $link ) {
+			return;
+		}
+
+		wp_redirect( $link, 301 ); // Permanent redirect
+		exit;
+	}
+}
+
+/**
  * Set up global post data.
  *
  * @since 1.5.0
Index: src/wp-includes/taxonomy.php
===================================================================
--- src/wp-includes/taxonomy.php	(revision 39646)
+++ src/wp-includes/taxonomy.php	(working copy)
@@ -2574,6 +2574,7 @@
 
 	// First, get all of the original args
 	$term = get_term( $term_id, $taxonomy );
+	$term_before = $term;
 
 	if ( is_wp_error( $term ) ) {
 		return $term;
@@ -2769,16 +2770,20 @@
 
 	clean_term_cache($term_id, $taxonomy);
 
+	$term_after = get_term( $term_id );
+
 	/**
 	 * Fires after a term has been updated, and the term cache has been cleaned.
 	 *
 	 * @since 2.3.0
 	 *
-	 * @param int    $term_id  Term ID.
-	 * @param int    $tt_id    Term taxonomy ID.
-	 * @param string $taxonomy Taxonomy slug.
+	 * @param int     $term_id      Term ID.
+	 * @param int     $tt_id        Term taxonomy ID.
+	 * @param string  $taxonomy     Taxonomy slug.
+	 * @param WP_Term $term_after   Term object following the update.
+	 * @param WP_Term $term_before  Term object before the update.
 	 */
-	do_action( "edited_term", $term_id, $tt_id, $taxonomy );
+	do_action( "edited_term", $term_id, $tt_id, $taxonomy, $term_after, $term_before );
 
 	/**
 	 * Fires after a term for a specific taxonomy has been updated, and the term
@@ -4227,3 +4232,43 @@
 
 	return $parent;
 }
+
+/**
+ * Check for changed slugs for the term and save the old slug.
+ *
+ * The function is used when a term is updated,
+ * by comparing the current and previous term objects.
+ *
+ * If the slug was changed and not already part of the old slugs then it will be
+ * added to the term meta field ('_wp_old_slug') for storing old slugs for
+ * that term.
+ *
+ * The most logically usage of this function is redirecting changed term objects, so
+ * that those that linked to a changed term will be redirected to the new term.
+ *
+ * @since 4.7.1
+ *
+ * @param int      $term_id      Term ID.
+ * @param int      $tt_id        Taxonomy Term ID.
+ * @param string   $taxonomy     Taxonomy
+ * @param WP_Term  $term_after   Term object following the update
+ * @param WP_Term  $term_before  Term object before the update
+ */
+function wp_check_for_changed_term_slugs( $term_id, $tt_id, $taxonomy, $term_after, $term_before ) {
+	// Don't bother if it hasn't changed.
+	if ( $term_after->slug == $term_before->slug ) {
+		return;
+	}
+
+	$old_slugs = (array) get_term_meta( $term_id, '_wp_old_slug' );
+
+	// If we haven't added this old slug before, add it now.
+	if ( ! empty( $term_before->slug ) && ! in_array( $term_before->slug, $old_slugs ) ) {
+		add_term_meta( $term_id, '_wp_old_slug', $term_before->slug );
+	}
+
+	// If the new slug was used previously, delete it from the list.
+	if ( in_array( $term_after->slug, $old_slugs ) ) {
+		delete_term_meta( $term_id, '_wp_old_slug', $term_after->slug );
+	}
+}
Index: tests/phpunit/tests/rewrite/oldTermSlugRedirect.php
===================================================================
--- tests/phpunit/tests/rewrite/oldTermSlugRedirect.php	(nonexistent)
+++ tests/phpunit/tests/rewrite/oldTermSlugRedirect.php	(working copy)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @group rewrite
+ * @ticket 15953
+ */
+class Tests_Rewrite_OldTermSlugRedirect extends WP_UnitTestCase {
+	protected $old_term_slug_redirect_url;
+
+	protected $term_id;
+
+	public function setUp() {
+		global $wp_rewrite;
+
+		parent::setUp();
+
+		$this->set_permalink_structure( '/%postname%/' );
+
+		register_taxonomy( 'wptests_tax', array( 'post' ) );
+
+		$this->term_id = self::factory()->term->create( array(
+			'taxonomy' => 'wptests_tax',
+			'name' => 'Foo Bar',
+			'slug' => 'foo-bar'
+		) );
+
+		$wp_rewrite->flush_rules();
+
+		add_filter( 'old_term_slug_redirect_url', array( $this, 'filter_old_term_slug_redirect_url' ), 10, 1 );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+
+		$this->old_term_slug_redirect_url = null;
+
+		remove_filter( 'old_term_slug_redirect_url', array( $this, 'filter_old_term_slug_redirect_url' ), 10 );
+	}
+
+	public function test_old_term_slug_redirect() {
+		$old_permalink = user_trailingslashit( get_term_link( $this->term_id, 'wptests_tax' ) );
+
+		wp_update_term( $this->term_id, 'wptests_tax', array(
+			'slug' => 'bar-baz'
+		) );
+
+		$permalink = user_trailingslashit( get_term_link( $this->term_id, 'wptests_tax' ) );
+
+		$this->go_to( $old_permalink );
+		wp_old_term_slug_redirect();
+		$this->assertEquals( $permalink, $this->old_term_slug_redirect_url );
+	}
+
+	public function test_old_slug_doesnt_redirect_when_reused() {
+		$old_permalink = user_trailingslashit( get_term_link( $this->term_id ) );
+
+		wp_update_term( $this->term_id, 'wptests_tax', array(
+			'slug' => 'bar-baz',
+		) );
+
+		$new_term_id = self::factory()->term->create( array(
+			'taxonomy' => 'wptests_tax',
+			'name'     => 'Foo Bar',
+			'slug'     => 'foo-bar'
+		) );
+
+		$permalink = user_trailingslashit( get_term_link( $new_term_id ) );
+
+		$this->assertEquals( $old_permalink, $permalink );
+
+		$this->go_to( $old_permalink );
+		wp_old_term_slug_redirect();
+		$this->assertNull( $this->old_term_slug_redirect_url );
+	}
+
+	public function filter_old_term_slug_redirect_url( $url ) {
+		$this->old_term_slug_redirect_url = $url;
+		return false;
+	}
+}
