diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 2e4801a55..9110956d1 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1224,9 +1224,6 @@ Rails/ActiveRecordCallbacksOrder:
 Rails/ApplicationController:
   Exclude:
     - 'app/controllers/health_controller.rb'
-    - 'app/controllers/well_known/host_meta_controller.rb'
-    - 'app/controllers/well_known/nodeinfo_controller.rb'
-    - 'app/controllers/well_known/webfinger_controller.rb'
 
 # Configuration parameters: Database, Include.
 # SupportedDatabases: mysql, postgresql
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 56229fd05..7cdc40fb1 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -7,8 +7,9 @@ class AccountsController < ApplicationController
   include AccountControllerConcern
   include SignatureAuthentication
 
+  vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
+
   before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
-  before_action :set_cache_headers
 
   skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
   skip_before_action :require_functional!, unless: :whitelist_mode?
diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb
index b8a7e0ab9..388d4b9e1 100644
--- a/app/controllers/activitypub/base_controller.rb
+++ b/app/controllers/activitypub/base_controller.rb
@@ -7,10 +7,6 @@ class ActivityPub::BaseController < Api::BaseController
 
   private
 
-  def set_cache_headers
-    response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
-  end
-
   def skip_temporary_suspension_response?
     false
   end
diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb
index d94a285ea..2188eb72a 100644
--- a/app/controllers/activitypub/collections_controller.rb
+++ b/app/controllers/activitypub/collections_controller.rb
@@ -4,11 +4,12 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
   include SignatureVerification
   include AccountOwnedConcern
 
+  vary_by -> { 'Signature' if authorized_fetch_mode? }
+
   before_action :require_account_signature!, if: :authorized_fetch_mode?
   before_action :set_items
   before_action :set_size
   before_action :set_type
-  before_action :set_cache_headers
 
   def show
     expires_in 3.minutes, public: public_fetch_mode?
diff --git a/app/controllers/activitypub/followers_synchronizations_controller.rb b/app/controllers/activitypub/followers_synchronizations_controller.rb
index 4e445bcb1..976caa344 100644
--- a/app/controllers/activitypub/followers_synchronizations_controller.rb
+++ b/app/controllers/activitypub/followers_synchronizations_controller.rb
@@ -4,9 +4,10 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
   include SignatureVerification
   include AccountOwnedConcern
 
+  vary_by -> { 'Signature' if authorized_fetch_mode? }
+
   before_action :require_account_signature!
   before_action :set_items
-  before_action :set_cache_headers
 
   def show
     expires_in 0, public: false
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 60d201f76..bf10ba762 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -6,9 +6,10 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   include SignatureVerification
   include AccountOwnedConcern
 
+  vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? }
+
   before_action :require_account_signature!, if: :authorized_fetch_mode?
   before_action :set_statuses
-  before_action :set_cache_headers
 
   def show
     if page_requested?
@@ -16,6 +17,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
     else
       expires_in(3.minutes, public: public_fetch_mode?)
     end
+
     render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
   end
 
@@ -80,8 +82,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   def set_account
     @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
   end
-
-  def set_cache_headers
-    response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
-  end
 end
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
index 8e0f9de2e..c38ff89d1 100644
--- a/app/controllers/activitypub/replies_controller.rb
+++ b/app/controllers/activitypub/replies_controller.rb
@@ -7,9 +7,10 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
 
   DESCENDANTS_LIMIT = 60
 
+  vary_by -> { 'Signature' if authorized_fetch_mode? }
+
   before_action :require_account_signature!, if: :authorized_fetch_mode?
   before_action :set_status
-  before_action :set_cache_headers
   before_action :set_replies
 
   def index
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index 5b7a7ec11..4b5afbe15 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -8,6 +8,8 @@ module Admin
     layout 'admin'
 
     before_action :set_body_classes
+    before_action :set_cache_headers
+
     after_action :verify_authorized
 
     private
@@ -16,6 +18,10 @@ module Admin
       @body_classes = 'admin'
     end
 
+    def set_cache_headers
+      response.cache_control.replace(private: true, no_store: true)
+    end
+
     def set_user
       @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
     end
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 41f3ce2ee..d62416c53 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -12,7 +12,7 @@ class Api::BaseController < ApplicationController
 
   before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
   before_action :require_not_suspended!
-  before_action :set_cache_headers
+  before_action :set_cache_control_defaults
 
   protect_from_forgery with: :null_session
 
@@ -148,8 +148,8 @@ class Api::BaseController < ApplicationController
     doorkeeper_authorize!(*scopes) if doorkeeper_token
   end
 
-  def set_cache_headers
-    response.headers['Cache-Control'] = 'private, no-store'
+  def set_cache_control_defaults
+    response.cache_control.replace(private: true, no_store: true)
   end
 
   def disallow_unauthenticated_api_access?
diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb
index 08b3474cc..380dbe8bf 100644
--- a/app/controllers/api/v1/custom_emojis_controller.rb
+++ b/app/controllers/api/v1/custom_emojis_controller.rb
@@ -1,8 +1,6 @@
 # frozen_string_literal: true
 
 class Api::V1::CustomEmojisController < Api::BaseController
-  skip_before_action :set_cache_headers
-
   def index
     expires_in 3.minutes, public: true
     render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb
index bad61425a..7ccfec703 100644
--- a/app/controllers/api/v1/instances/activity_controller.rb
+++ b/app/controllers/api/v1/instances/activity_controller.rb
@@ -3,7 +3,6 @@
 class Api::V1::Instances::ActivityController < Api::BaseController
   before_action :require_enabled_api!
 
-  skip_before_action :set_cache_headers
   skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 
   def show
diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb
index 2877fec52..b2669a84e 100644
--- a/app/controllers/api/v1/instances/peers_controller.rb
+++ b/app/controllers/api/v1/instances/peers_controller.rb
@@ -3,7 +3,6 @@
 class Api::V1::Instances::PeersController < Api::BaseController
   before_action :require_enabled_api!
 
-  skip_before_action :set_cache_headers
   skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 
   def index
diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb
index 913319a86..3cdb404cb 100644
--- a/app/controllers/api/v1/instances_controller.rb
+++ b/app/controllers/api/v1/instances_controller.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 class Api::V1::InstancesController < Api::BaseController
-  skip_before_action :set_cache_headers
   skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 
   def show
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index b55f7f309..f9d30c1eb 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -152,6 +152,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   end
 
   def set_cache_headers
-    response.headers['Cache-Control'] = 'private, no-store'
+    response.cache_control.replace(private: true, no_store: true)
   end
 end
diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb
index a5a9ba3e1..ffede5839 100644
--- a/app/controllers/concerns/cache_concern.rb
+++ b/app/controllers/concerns/cache_concern.rb
@@ -155,8 +155,16 @@ module CacheConcern
     end
   end
 
+  class_methods do
+    def vary_by(value)
+      before_action do |controller|
+        response.headers['Vary'] = value.respond_to?(:call) ? controller.instance_exec(&value) : value
+      end
+    end
+  end
+
   def render_with_cache(**options)
-    raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
+    raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
 
     key        = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
     expires_in = options.delete(:expires_in) || 3.minutes
@@ -176,10 +184,6 @@ module CacheConcern
     end
   end
 
-  def set_cache_headers
-    response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
-  end
-
   def cache_collection(raw, klass)
     return raw unless klass.respond_to?(:with_includes)
 
diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb
index 9270c467d..e7a02ea89 100644
--- a/app/controllers/custom_css_controller.rb
+++ b/app/controllers/custom_css_controller.rb
@@ -1,18 +1,8 @@
 # frozen_string_literal: true
 
-class CustomCssController < ApplicationController
-  skip_before_action :store_current_location
-  skip_before_action :require_functional!
-  skip_before_action :update_user_sign_in
-  skip_before_action :set_session_activity
-
-  skip_around_action :set_locale
-
-  before_action :set_cache_headers
-
+class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
   def show
     expires_in 3.minutes, public: true
-    request.session_options[:skip] = true
     render content_type: 'text/css'
   end
 end
diff --git a/app/controllers/disputes/base_controller.rb b/app/controllers/disputes/base_controller.rb
index 865146b5c..1054f3db8 100644
--- a/app/controllers/disputes/base_controller.rb
+++ b/app/controllers/disputes/base_controller.rb
@@ -9,10 +9,15 @@ class Disputes::BaseController < ApplicationController
 
   before_action :set_body_classes
   before_action :authenticate_user!
+  before_action :set_cache_headers
 
   private
 
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb
index 41f1e1c5c..72bc56de0 100644
--- a/app/controllers/emojis_controller.rb
+++ b/app/controllers/emojis_controller.rb
@@ -2,15 +2,12 @@
 
 class EmojisController < ApplicationController
   before_action :set_emoji
-  before_action :set_cache_headers
+
+  vary_by -> { 'Signature' if authorized_fetch_mode? }
 
   def show
-    respond_to do |format|
-      format.json do
-        expires_in 3.minutes, public: true
-        render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
-      end
-    end
+    expires_in 3.minutes, public: true
+    render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
   end
 
   private
diff --git a/app/controllers/filters/statuses_controller.rb b/app/controllers/filters/statuses_controller.rb
index 7779c6d95..94993f938 100644
--- a/app/controllers/filters/statuses_controller.rb
+++ b/app/controllers/filters/statuses_controller.rb
@@ -7,6 +7,7 @@ class Filters::StatusesController < ApplicationController
   before_action :set_filter
   before_action :set_status_filters
   before_action :set_body_classes
+  before_action :set_cache_headers
 
   PER_PAGE = 20
 
@@ -44,4 +45,8 @@ class Filters::StatusesController < ApplicationController
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index cc5cb5d9f..1881dd5a0 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -6,6 +6,7 @@ class FiltersController < ApplicationController
   before_action :authenticate_user!
   before_action :set_filter, only: [:edit, :update, :destroy]
   before_action :set_body_classes
+  before_action :set_cache_headers
 
   def index
     @filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
@@ -54,4 +55,8 @@ class FiltersController < ApplicationController
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 9ced18449..3068c07e3 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -5,8 +5,9 @@ class FollowerAccountsController < ApplicationController
   include SignatureVerification
   include WebAppControllerConcern
 
+  vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
+
   before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
-  before_action :set_cache_headers
 
   skip_around_action :set_locale, if: -> { request.format == :json }
   skip_before_action :require_functional!, unless: :whitelist_mode?
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index febd13c97..022884193 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -5,8 +5,9 @@ class FollowingAccountsController < ApplicationController
   include SignatureVerification
   include WebAppControllerConcern
 
+  vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
+
   before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
-  before_action :set_cache_headers
 
   skip_around_action :set_locale, if: -> { request.format == :json }
   skip_before_action :require_functional!, unless: :whitelist_mode?
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 8d92147e2..9bc5164d5 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -7,6 +7,7 @@ class InvitesController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_body_classes
+  before_action :set_cache_headers
 
   def index
     authorize :invite, :create?
@@ -49,4 +50,8 @@ class InvitesController < ApplicationController
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/manifests_controller.rb b/app/controllers/manifests_controller.rb
index 960510f60..593b76c53 100644
--- a/app/controllers/manifests_controller.rb
+++ b/app/controllers/manifests_controller.rb
@@ -1,9 +1,6 @@
 # frozen_string_literal: true
 
-class ManifestsController < ApplicationController
-  skip_before_action :store_current_location
-  skip_before_action :require_functional!
-
+class ManifestsController < ActionController::Base # rubocop:disable Rails/ApplicationController
   def show
     expires_in 3.minutes, public: true
     render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance'
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index 5449cfb1a..66e774425 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -34,6 +34,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
   end
 
   def set_cache_headers
-    response.headers['Cache-Control'] = 'private, no-store'
+    response.cache_control.replace(private: true, no_store: true)
   end
 end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index 45151cdd7..e3a3c4fc1 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -7,6 +7,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
   before_action :authenticate_resource_owner!
   before_action :require_not_suspended!, only: :destroy
   before_action :set_body_classes
+  before_action :set_cache_headers
 
   skip_before_action :require_functional!
 
@@ -30,4 +31,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
   def require_not_suspended!
     forbidden if current_account.suspended?
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
index de5dc5879..e87b5a656 100644
--- a/app/controllers/relationships_controller.rb
+++ b/app/controllers/relationships_controller.rb
@@ -7,6 +7,7 @@ class RelationshipsController < ApplicationController
   before_action :set_accounts, only: :show
   before_action :set_relationships, only: :show
   before_action :set_body_classes
+  before_action :set_cache_headers
 
   helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
 
@@ -70,4 +71,8 @@ class RelationshipsController < ApplicationController
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
index 8722fd64a..64dcd47d1 100644
--- a/app/controllers/settings/base_controller.rb
+++ b/app/controllers/settings/base_controller.rb
@@ -14,7 +14,7 @@ class Settings::BaseController < ApplicationController
   end
 
   def set_cache_headers
-    response.headers['Cache-Control'] = 'private, no-store'
+    response.cache_control.replace(private: true, no_store: true)
   end
 
   def require_not_suspended!
diff --git a/app/controllers/statuses_cleanup_controller.rb b/app/controllers/statuses_cleanup_controller.rb
index e912967fd..19ae971ce 100644
--- a/app/controllers/statuses_cleanup_controller.rb
+++ b/app/controllers/statuses_cleanup_controller.rb
@@ -6,6 +6,7 @@ class StatusesCleanupController < ApplicationController
   before_action :authenticate_user!
   before_action :set_policy
   before_action :set_body_classes
+  before_action :set_cache_headers
 
   def show; end
 
@@ -36,4 +37,8 @@ class StatusesCleanupController < ApplicationController
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_cache_headers
+    response.cache_control.replace(private: true, no_store: true)
+  end
 end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index d369cd8e6..07927d119 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -6,11 +6,12 @@ class StatusesController < ApplicationController
   include Authorization
   include AccountOwnedConcern
 
+  vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
+
   before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
   before_action :set_status
   before_action :set_instance_presenter
   before_action :redirect_to_original, only: :show
-  before_action :set_cache_headers
   before_action :set_body_classes, only: :embed
 
   after_action :set_link_headers
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 4b747c9ad..4afc1aca5 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -7,6 +7,8 @@ class TagsController < ApplicationController
   PAGE_SIZE     = 20
   PAGE_SIZE_MAX = 200
 
+  vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
+
   before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
   before_action :authenticate_user!, if: :whitelist_mode?
   before_action :set_local
diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb
index 2fd6bc7cc..201da9fbc 100644
--- a/app/controllers/well_known/host_meta_controller.rb
+++ b/app/controllers/well_known/host_meta_controller.rb
@@ -1,11 +1,9 @@
 # frozen_string_literal: true
 
 module WellKnown
-  class HostMetaController < ActionController::Base
+  class HostMetaController < ActionController::Base # rubocop:disable Rails/ApplicationController
     include RoutingHelper
 
-    before_action { response.headers['Vary'] = 'Accept' }
-
     def show
       @webfinger_template = "#{webfinger_url}?resource={uri}"
       expires_in 3.days, public: true
diff --git a/app/controllers/well_known/nodeinfo_controller.rb b/app/controllers/well_known/nodeinfo_controller.rb
index 11a699ebc..ab6b8f5a4 100644
--- a/app/controllers/well_known/nodeinfo_controller.rb
+++ b/app/controllers/well_known/nodeinfo_controller.rb
@@ -1,11 +1,9 @@
 # frozen_string_literal: true
 
 module WellKnown
-  class NodeInfoController < ActionController::Base
+  class NodeInfoController < ActionController::Base # rubocop:disable Rails/ApplicationController
     include CacheConcern
 
-    before_action { response.headers['Vary'] = 'Accept' }
-
     def index
       expires_in 3.days, public: true
       render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo'
diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb
index 2b296ea3b..a06253f45 100644
--- a/app/controllers/well_known/webfinger_controller.rb
+++ b/app/controllers/well_known/webfinger_controller.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module WellKnown
-  class WebfingerController < ActionController::Base
+  class WebfingerController < ActionController::Base # rubocop:disable Rails/ApplicationController
     include RoutingHelper
 
     before_action :set_account
@@ -34,7 +34,12 @@ module WellKnown
     end
 
     def check_account_suspension
-      expires_in(3.minutes, public: true) && gone if @account.suspended_permanently?
+      gone if @account.suspended_permanently?
+    end
+
+    def gone
+      expires_in(3.minutes, public: true)
+      head 410
     end
 
     def bad_request
@@ -46,9 +51,5 @@ module WellKnown
       expires_in(3.minutes, public: true)
       head 404
     end
-
-    def gone
-      head 410
-    end
   end
 end
diff --git a/config/application.rb b/config/application.rb
index f0e65f443..171dab11f 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -43,6 +43,7 @@ require_relative '../lib/chewy/strategy/bypass_with_warning'
 require_relative '../lib/webpacker/manifest_extensions'
 require_relative '../lib/webpacker/helper_extensions'
 require_relative '../lib/rails/engine_extensions'
+require_relative '../lib/action_controller/conditional_get_extensions'
 require_relative '../lib/active_record/database_tasks_extensions'
 require_relative '../lib/active_record/batches'
 require_relative '../lib/simple_navigation/item_extensions'
diff --git a/lib/action_controller/conditional_get_extensions.rb b/lib/action_controller/conditional_get_extensions.rb
new file mode 100644
index 000000000..192ed341e
--- /dev/null
+++ b/lib/action_controller/conditional_get_extensions.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ActionController
+  module ConditionalGetExtensions
+    def expires_in(*)
+      # This backports a fix from Rails 7 so that a more private Cache-Control
+      # can be overriden by calling expires_in on a specific controller action
+      response.cache_control.delete(:no_store)
+
+      super
+    end
+  end
+end
+
+ActionController::ConditionalGet.prepend(ActionController::ConditionalGetExtensions)
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 9c38b3032..0cdccd70d 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -17,6 +17,10 @@ RSpec.describe AccountsController, type: :controller do
       expect(session).to be_empty
     end
 
+    it 'returns Vary header' do
+      expect(response.headers['Vary']).to include 'Accept'
+    end
+
     it 'returns public Cache-Control header' do
       expect(response.headers['Cache-Control']).to include 'public'
     end
diff --git a/spec/controllers/admin/base_controller_spec.rb b/spec/controllers/admin/base_controller_spec.rb
index 5fbf8777c..bfb9d2c7d 100644
--- a/spec/controllers/admin/base_controller_spec.rb
+++ b/spec/controllers/admin/base_controller_spec.rb
@@ -18,6 +18,14 @@ describe Admin::BaseController, type: :controller do
     expect(response).to have_http_status(403)
   end
 
+  it 'returns private cache control headers' do
+    routes.draw { get 'success' => 'admin/base#success' }
+    sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator')))
+    get :success
+
+    expect(response.headers['Cache-Control']).to include('private, no-store')
+  end
+
   it 'renders admin layout as a moderator' do
     routes.draw { get 'success' => 'admin/base#success' }
     sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator')))
diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb
index c286b8cbf..080eab3c0 100644
--- a/spec/controllers/api/base_controller_spec.rb
+++ b/spec/controllers/api/base_controller_spec.rb
@@ -15,6 +15,12 @@ describe Api::BaseController do
     end
   end
 
+  it 'returns private cache control headers by default' do
+    routes.draw { get 'success' => 'api/base#success' }
+    get :success
+    expect(response.headers['Cache-Control']).to include('private, no-store')
+  end
+
   describe 'forgery protection' do
     before do
       routes.draw { post 'success' => 'api/base#success' }
diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb
index 930f36250..02875ee9f 100644
--- a/spec/controllers/api/oembed_controller_spec.rb
+++ b/spec/controllers/api/oembed_controller_spec.rb
@@ -17,5 +17,9 @@ RSpec.describe Api::OEmbedController, type: :controller do
     it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index e3a00fa39..fffa7e06d 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -33,27 +33,42 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
   end
 
   describe 'GET #edit' do
-    it 'returns http success' do
+    before do
       request.env['devise.mapping'] = Devise.mappings[:user]
       sign_in(Fabricate(:user))
       get :edit
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control header' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'GET #update' do
-    it 'returns http success' do
+    let(:user) { Fabricate(:user) }
+
+    before do
       request.env['devise.mapping'] = Devise.mappings[:user]
-      sign_in(Fabricate(:user), scope: :user)
+      sign_in(user, scope: :user)
       post :update
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
 
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
+
     context 'when suspended' do
+      let(:user) { Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }) }
+
       it 'returns http forbidden' do
-        request.env['devise.mapping'] = Devise.mappings[:user]
-        sign_in(Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }), scope: :user)
-        post :update
         expect(response).to have_http_status(403)
       end
     end
diff --git a/spec/controllers/custom_css_controller_spec.rb b/spec/controllers/custom_css_controller_spec.rb
index 47fe6031f..99d36d21b 100644
--- a/spec/controllers/custom_css_controller_spec.rb
+++ b/spec/controllers/custom_css_controller_spec.rb
@@ -6,9 +6,25 @@ describe CustomCssController do
   render_views
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns public cache control header' do
+      expect(response.headers['Cache-Control']).to include('public')
+    end
+
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be_nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
   end
 end
diff --git a/spec/controllers/filters/statuses_controller_spec.rb b/spec/controllers/filters/statuses_controller_spec.rb
index 492361188..2c8061330 100644
--- a/spec/controllers/filters/statuses_controller_spec.rb
+++ b/spec/controllers/filters/statuses_controller_spec.rb
@@ -18,21 +18,27 @@ describe Filters::StatusesController do
 
     context 'with a signed in user' do
       context 'with the filter user signed in' do
-        before { sign_in(filter.account.user) }
+        before do
+          sign_in(filter.account.user)
+          get :index, params: { filter_id: filter }
+        end
 
         it 'returns http success' do
-          get :index, params: { filter_id: filter }
-
           expect(response).to have_http_status(200)
         end
+
+        it 'returns private cache control headers' do
+          expect(response.headers['Cache-Control']).to include('private, no-store')
+        end
       end
 
       context 'with another user signed in' do
-        before { sign_in(Fabricate(:user)) }
+        before do
+          sign_in(Fabricate(:user))
+          get :index, params: { filter_id: filter }
+        end
 
         it 'returns http not found' do
-          get :index, params: { filter_id: filter }
-
           expect(response).to have_http_status(404)
         end
       end
diff --git a/spec/controllers/filters_controller_spec.rb b/spec/controllers/filters_controller_spec.rb
index f68f87ba7..091f714bb 100644
--- a/spec/controllers/filters_controller_spec.rb
+++ b/spec/controllers/filters_controller_spec.rb
@@ -7,21 +7,28 @@ describe FiltersController do
 
   describe 'GET #index' do
     context 'with signed out user' do
-      it 'redirects' do
+      before do
         get :index
+      end
 
+      it 'redirects' do
         expect(response).to be_redirect
       end
     end
 
     context 'with a signed in user' do
-      before { sign_in(Fabricate(:user)) }
+      before do
+        sign_in(Fabricate(:user))
+        get :index
+      end
 
       it 'returns http success' do
-        get :index
-
         expect(response).to have_http_status(200)
       end
+
+      it 'returns private cache control headers' do
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
     end
   end
 end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 408c5e1b5..8718403bf 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -5,35 +5,40 @@ require 'rails_helper'
 describe InvitesController do
   render_views
 
+  let(:user) { Fabricate(:user) }
+
   before do
     sign_in user
   end
 
   describe 'GET #index' do
-    subject { get :index }
-
-    let(:user) { Fabricate(:user) }
-    let!(:invite) { Fabricate(:invite, user: user) }
+    before do
+      Fabricate(:invite, user: user)
+    end
 
     context 'when everyone can invite' do
       before do
         UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users])
+        get :index
       end
 
-      it 'renders index page' do
-        expect(subject).to render_template :index
-        expect(assigns(:invites)).to include invite
-        expect(assigns(:invites).count).to eq 1
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'returns private cache control headers' do
+        expect(response.headers['Cache-Control']).to include('private, no-store')
       end
     end
 
     context 'when not everyone can invite' do
       before do
         UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users])
+        get :index
       end
 
-      it 'returns 403' do
-        expect(subject).to have_http_status 403
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(403)
       end
     end
   end
@@ -42,8 +47,6 @@ describe InvitesController do
     subject { post :create, params: { invite: { max_uses: '10', expires_in: 1800 } } }
 
     context 'when everyone can invite' do
-      let(:user) { Fabricate(:user) }
-
       before do
         UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users])
       end
@@ -56,26 +59,28 @@ describe InvitesController do
     end
 
     context 'when not everyone can invite' do
-      let(:user) { Fabricate(:user) }
-
       before do
         UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users])
       end
 
-      it 'returns 403' do
-        expect(subject).to have_http_status 403
+      it 'returns http forbidden' do
+        expect(subject).to have_http_status(403)
       end
     end
   end
 
   describe 'DELETE #create' do
-    subject { delete :destroy, params: { id: invite.id } }
+    let(:invite) { Fabricate(:invite, user: user, expires_at: nil) }
 
-    let(:user) { Fabricate(:user) }
-    let!(:invite) { Fabricate(:invite, user: user, expires_at: nil) }
+    before do
+      delete :destroy, params: { id: invite.id }
+    end
+
+    it 'redirects' do
+      expect(response).to redirect_to invites_path
+    end
 
     it 'expires invite' do
-      expect(subject).to redirect_to invites_path
       expect(invite.reload).to be_expired
     end
   end
diff --git a/spec/controllers/manifests_controller_spec.rb b/spec/controllers/manifests_controller_spec.rb
index ecd6957fc..d0699c438 100644
--- a/spec/controllers/manifests_controller_spec.rb
+++ b/spec/controllers/manifests_controller_spec.rb
@@ -7,11 +7,24 @@ describe ManifestsController do
 
   describe 'GET #show' do
     before do
-      get :show, format: :json
+      get :show
     end
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns public cache control header' do
+      expect(response.headers['Cache-Control']).to include('public')
+    end
+
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be_nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
   end
 end
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index c5eeea397..6f087625c 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -31,6 +31,11 @@ RSpec.describe Oauth::AuthorizationsController, type: :controller do
         expect(response).to have_http_status(200)
       end
 
+      it 'returns private cache control headers' do
+        subject
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
+
       it 'gives options to authorize and deny' do
         subject
         expect(response.body).to match(/Authorize/)
diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb
index 885bfa35b..b54610604 100644
--- a/spec/controllers/oauth/authorized_applications_controller_spec.rb
+++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb
@@ -27,6 +27,11 @@ describe Oauth::AuthorizedApplicationsController do
         expect(response).to have_http_status(200)
       end
 
+      it 'returns private cache control headers' do
+        subject
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
+
       include_examples 'stores location for user'
     end
 
diff --git a/spec/controllers/relationships_controller_spec.rb b/spec/controllers/relationships_controller_spec.rb
index 53a5daa51..9495fc214 100644
--- a/spec/controllers/relationships_controller_spec.rb
+++ b/spec/controllers/relationships_controller_spec.rb
@@ -7,42 +7,39 @@ describe RelationshipsController do
 
   let(:user) { Fabricate(:user) }
 
-  shared_examples 'authenticate user' do
-    it 'redirects when not signed in' do
-      expect(subject).to redirect_to '/auth/sign_in'
-    end
-  end
-
   describe 'GET #show' do
-    subject { get :show, params: { page: 2, relationship: 'followed_by' } }
+    context 'when signed in' do
+      before do
+        sign_in user, scope: :user
+        get :show, params: { page: 2, relationship: 'followed_by' }
+      end
 
-    it 'assigns @accounts' do
-      Fabricate(:account, domain: 'old').follow!(user.account)
-      Fabricate(:account, domain: 'recent').follow!(user.account)
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
 
-      sign_in user, scope: :user
-      subject
-
-      assigned = assigns(:accounts).per(1).to_a
-      expect(assigned.size).to eq 1
-      expect(assigned[0].domain).to eq 'old'
+      it 'returns private cache control headers' do
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
     end
 
-    it 'returns http success' do
-      sign_in user, scope: :user
-      subject
-      expect(response).to have_http_status(200)
-    end
+    context 'when not signed in' do
+      before do
+        get :show, params: { page: 2, relationship: 'followed_by' }
+      end
 
-    include_examples 'authenticate user'
+      it 'redirects when not signed in' do
+        expect(response).to redirect_to '/auth/sign_in'
+      end
+    end
   end
 
   describe 'PATCH #update' do
-    let(:poopfeast) { Fabricate(:account, username: 'poopfeast', domain: 'example.com') }
+    let(:alice) { Fabricate(:account, username: 'alice', domain: 'example.com') }
 
     shared_examples 'redirects back to followers page' do
       it 'redirects back to followers page' do
-        poopfeast.follow!(user.account)
+        alice.follow!(user.account)
 
         sign_in user, scope: :user
         subject
@@ -58,27 +55,36 @@ describe RelationshipsController do
     end
 
     context 'when select parameter is provided' do
-      subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, remove_domains_from_followers: '' } }
+      subject { patch :update, params: { form_account_batch: { account_ids: [alice.id] }, remove_domains_from_followers: '' } }
 
       it 'soft-blocks followers from selected domains' do
-        poopfeast.follow!(user.account)
+        alice.follow!(user.account)
 
         sign_in user, scope: :user
         subject
 
-        expect(poopfeast.following?(user.account)).to be false
+        expect(alice.following?(user.account)).to be false
       end
 
       it 'does not unfollow users from selected domains' do
-        user.account.follow!(poopfeast)
+        user.account.follow!(alice)
 
         sign_in user, scope: :user
         subject
 
-        expect(user.account.following?(poopfeast)).to be true
+        expect(user.account.following?(alice)).to be true
+      end
+
+      context 'when not signed in' do
+        before do
+          subject
+        end
+
+        it 'redirects when not signed in' do
+          expect(response).to redirect_to '/auth/sign_in'
+        end
       end
 
-      include_examples 'authenticate user'
       include_examples 'redirects back to followers page'
     end
   end
diff --git a/spec/controllers/settings/aliases_controller_spec.rb b/spec/controllers/settings/aliases_controller_spec.rb
index ef8724faf..9636c1ac5 100644
--- a/spec/controllers/settings/aliases_controller_spec.rb
+++ b/spec/controllers/settings/aliases_controller_spec.rb
@@ -13,10 +13,17 @@ describe Settings::AliasesController do
   end
 
   describe 'GET #index' do
-    it 'returns http success' do
+    before do
       get :index
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'POST #create' do
diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb
index 5c6b04a15..e12628a18 100644
--- a/spec/controllers/settings/applications_controller_spec.rb
+++ b/spec/controllers/settings/applications_controller_spec.rb
@@ -13,13 +13,17 @@ describe Settings::ApplicationsController do
   end
 
   describe 'GET #index' do
-    let!(:other_app) { Fabricate(:application) }
-
-    it 'shows apps' do
+    before do
+      Fabricate(:application)
       get :index
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
-      expect(assigns(:applications)).to include(app)
-      expect(assigns(:applications)).to_not include(other_app)
+    end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
     end
   end
 
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
index a7edac6a9..7ee18f9fb 100644
--- a/spec/controllers/settings/deletes_controller_spec.rb
+++ b/spec/controllers/settings/deletes_controller_spec.rb
@@ -11,20 +11,27 @@ describe Settings::DeletesController do
 
       before do
         sign_in user, scope: :user
+        get :show
       end
 
       it 'renders confirmation page' do
-        get :show
         expect(response).to have_http_status(200)
       end
 
+      it 'returns private cache control headers' do
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
+
       context 'when suspended' do
         let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
 
         it 'returns http forbidden' do
-          get :show
           expect(response).to have_http_status(403)
         end
+
+        it 'returns private cache control headers' do
+          expect(response.headers['Cache-Control']).to include('private, no-store')
+        end
       end
     end
 
diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb
index a46fe095d..6a4202131 100644
--- a/spec/controllers/settings/exports_controller_spec.rb
+++ b/spec/controllers/settings/exports_controller_spec.rb
@@ -11,16 +11,16 @@ describe Settings::ExportsController do
 
       before do
         sign_in user, scope: :user
+        get :show
       end
 
-      it 'renders export' do
-        get :show
-
-        export = assigns(:export)
-        expect(export).to be_instance_of Export
-        expect(export.account).to eq user.account
+      it 'returns http success' do
         expect(response).to have_http_status(200)
       end
+
+      it 'returns private cache control headers' do
+        expect(response.headers['Cache-Control']).to include('private, no-store')
+      end
     end
 
     context 'when not signed in' do
diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb
index 78973df2b..98ba897e4 100644
--- a/spec/controllers/settings/imports_controller_spec.rb
+++ b/spec/controllers/settings/imports_controller_spec.rb
@@ -10,10 +10,17 @@ RSpec.describe Settings::ImportsController, type: :controller do
   end
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'POST #create' do
diff --git a/spec/controllers/settings/login_activities_controller_spec.rb b/spec/controllers/settings/login_activities_controller_spec.rb
index 6f1f3de31..80c8f484c 100644
--- a/spec/controllers/settings/login_activities_controller_spec.rb
+++ b/spec/controllers/settings/login_activities_controller_spec.rb
@@ -12,9 +12,16 @@ describe Settings::LoginActivitiesController do
   end
 
   describe 'GET #index' do
-    it 'returns http success' do
+    before do
       get :index
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 end
diff --git a/spec/controllers/settings/migration/redirects_controller_spec.rb b/spec/controllers/settings/migration/redirects_controller_spec.rb
index 54897bb7f..aa6df64cf 100644
--- a/spec/controllers/settings/migration/redirects_controller_spec.rb
+++ b/spec/controllers/settings/migration/redirects_controller_spec.rb
@@ -12,10 +12,17 @@ describe Settings::Migration::RedirectsController do
   end
 
   describe 'GET #new' do
-    it 'returns http success' do
+    before do
       get :new
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'POST #create' do
diff --git a/spec/controllers/settings/preferences/appearance_controller_spec.rb b/spec/controllers/settings/preferences/appearance_controller_spec.rb
index df0237a6b..083bf4954 100644
--- a/spec/controllers/settings/preferences/appearance_controller_spec.rb
+++ b/spec/controllers/settings/preferences/appearance_controller_spec.rb
@@ -12,11 +12,17 @@ describe Settings::Preferences::AppearanceController do
   end
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
 
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'PUT #update' do
diff --git a/spec/controllers/settings/preferences/notifications_controller_spec.rb b/spec/controllers/settings/preferences/notifications_controller_spec.rb
index 29b7b6aec..6a04df9ed 100644
--- a/spec/controllers/settings/preferences/notifications_controller_spec.rb
+++ b/spec/controllers/settings/preferences/notifications_controller_spec.rb
@@ -12,10 +12,17 @@ describe Settings::Preferences::NotificationsController do
   end
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'PUT #update' do
diff --git a/spec/controllers/settings/preferences/other_controller_spec.rb b/spec/controllers/settings/preferences/other_controller_spec.rb
index 249d1b5b5..750510b04 100644
--- a/spec/controllers/settings/preferences/other_controller_spec.rb
+++ b/spec/controllers/settings/preferences/other_controller_spec.rb
@@ -12,10 +12,17 @@ describe Settings::Preferences::OtherController do
   end
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'PUT #update' do
diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb
index 563e60271..52ae1f519 100644
--- a/spec/controllers/settings/profiles_controller_spec.rb
+++ b/spec/controllers/settings/profiles_controller_spec.rb
@@ -13,10 +13,17 @@ RSpec.describe Settings::ProfilesController, type: :controller do
   end
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'PUT #update' do
diff --git a/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb b/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb
index 153eca1a5..3e61912ad 100644
--- a/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentication_methods_controller_spec.rb
@@ -26,23 +26,25 @@ describe Settings::TwoFactorAuthenticationMethodsController do
       describe 'when user has enabled otp' do
         before do
           user.update(otp_required_for_login: true)
+          get :index
         end
 
         it 'returns http success' do
-          get :index
-
           expect(response).to have_http_status(200)
         end
+
+        it 'returns private cache control headers' do
+          expect(response.headers['Cache-Control']).to include('private, no-store')
+        end
       end
 
       describe 'when user has not enabled otp' do
         before do
           user.update(otp_required_for_login: false)
+          get :index
         end
 
         it 'redirects to enable otp' do
-          get :index
-
           expect(response).to redirect_to(settings_otp_authentication_path)
         end
       end
diff --git a/spec/controllers/statuses_cleanup_controller_spec.rb b/spec/controllers/statuses_cleanup_controller_spec.rb
index 969778bbf..693260f92 100644
--- a/spec/controllers/statuses_cleanup_controller_spec.rb
+++ b/spec/controllers/statuses_cleanup_controller_spec.rb
@@ -11,19 +11,32 @@ RSpec.describe StatusesCleanupController, type: :controller do
   end
 
   describe 'GET #show' do
-    it 'returns http success' do
+    before do
       get :show
+    end
+
+    it 'returns http success' do
       expect(response).to have_http_status(200)
     end
+
+    it 'returns private cache control headers' do
+      expect(response.headers['Cache-Control']).to include('private, no-store')
+    end
   end
 
   describe 'PUT #update' do
-    it 'updates the account status cleanup policy' do
+    before do
       put :update, params: { account_statuses_cleanup_policy: { enabled: true, min_status_age: 2.weeks.seconds, keep_direct: false, keep_polls: true } }
-      expect(response).to redirect_to(statuses_cleanup_path)
+    end
+
+    it 'updates the account status cleanup policy' do
       expect(@user.account.statuses_cleanup_policy.enabled).to be true
       expect(@user.account.statuses_cleanup_policy.keep_direct).to be false
       expect(@user.account.statuses_cleanup_policy.keep_polls).to be true
     end
+
+    it 'redirects' do
+      expect(response).to redirect_to(statuses_cleanup_path)
+    end
   end
 end
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index c8b503d68..f0a40707f 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -15,6 +15,10 @@ describe StatusesController do
       expect(session).to be_empty
     end
 
+    it 'returns Vary header' do
+      expect(response.headers['Vary']).to include 'Accept'
+    end
+
     it 'returns public Cache-Control header' do
       expect(response.headers['Cache-Control']).to include 'public'
     end
diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb
index 8a3fa0bf8..9287a6ab5 100644
--- a/spec/controllers/tags_controller_spec.rb
+++ b/spec/controllers/tags_controller_spec.rb
@@ -6,21 +6,50 @@ RSpec.describe TagsController, type: :controller do
   render_views
 
   describe 'GET #show' do
-    let!(:tag)     { Fabricate(:tag, name: 'test') }
-    let!(:local)   { Fabricate(:status, tags: [tag], text: 'local #test') }
-    let!(:remote)  { Fabricate(:status, tags: [tag], text: 'remote #test', account: Fabricate(:account, domain: 'remote')) }
-    let!(:late)    { Fabricate(:status, tags: [tag], text: 'late #test') }
+    let(:format) { 'html' }
+    let(:tag) { Fabricate(:tag, name: 'test') }
+    let(:tag_name) { tag&.name }
+
+    before do
+      get :show, params: { id: tag_name, format: format }
+    end
 
     context 'when tag exists' do
-      it 'returns http success' do
-        get :show, params: { id: 'test', max_id: late.id }
-        expect(response).to have_http_status(200)
+      context 'when requested as HTML' do
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns Vary header' do
+          expect(response.headers['Vary']).to eq 'Accept'
+        end
+
+        it 'returns public Cache-Control header' do
+          expect(response.headers['Cache-Control']).to include 'public'
+        end
+      end
+
+      context 'when requested as JSON' do
+        let(:format) { 'json' }
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns Vary header' do
+          expect(response.headers['Vary']).to eq 'Accept'
+        end
+
+        it 'returns public Cache-Control header' do
+          expect(response.headers['Cache-Control']).to include 'public'
+        end
       end
     end
 
     context 'when tag does not exist' do
+      let(:tag_name) { 'hoge' }
+
       it 'returns http not found' do
-        get :show, params: { id: 'none' }
         expect(response).to have_http_status(404)
       end
     end