diff --git a/Gemfile b/Gemfile
index 50287ffa77abd681722ce1d7cfafdec1c2d05ea9..f5bd290e37d02024b04504d730efff6ced73455b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,7 @@ gem 'mysql2', '~> 0.5.4'
 gem 'puma', '~> 5.6'
 gem 'rails', '~> 7.0.0'
 gem 'rails-html-sanitizer', '~> 1.4'
-gem 'redis', '~> 5.0'
+gem 'redis', '~> 4.8'
 gem 'rotp', '~> 6.2'
 gem 'sass-rails', '~> 6.0'
 gem 'sprockets', '~> 4.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 429090e1c67d1c53060b181df8f6d41442a03c23..4bac246fe5d118dfdf86419a81abbaa128a4c627 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -107,7 +107,6 @@ GEM
     coffee-script-source (1.12.2)
     commonmarker (0.23.5)
     concurrent-ruby (1.1.10)
-    connection_pool (2.2.5)
     counter_culture (3.2.1)
       activerecord (>= 4.2)
       activesupport (>= 4.2)
@@ -262,10 +261,7 @@ GEM
     rb-fsevent (0.11.2)
     rb-inotify (0.10.1)
       ffi (~> 1.0)
-    redis (5.0.4)
-      redis-client (>= 0.7.4)
-    redis-client (0.8.0)
-      connection_pool
+    redis (4.8.0)
     regexp_parser (2.5.0)
     responders (3.0.1)
       actionpack (>= 5.0)
@@ -396,7 +392,7 @@ DEPENDENCIES
   rails (~> 7.0.0)
   rails-controller-testing (~> 1.0)
   rails-html-sanitizer (~> 1.4)
-  redis (~> 5.0)
+  redis (~> 4.8)
   reverse_markdown (~> 2.1)
   rmagick
   rotp (~> 6.2)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 68cd945fbe804a805dde618a63eba9e8d2c80fcb..ce8c81040301ea46cc73e07d5a4da8535819618b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -268,7 +268,9 @@ class ApplicationController < ActionController::Base
 
     @hot_questions = Rails.cache.fetch('hot_questions', expires_in: 4.hours) do
       Rack::MiniProfiler.step 'hot_questions: cache miss' do
-        Post.undeleted.where(last_activity: (Rails.env.development? ? 365 : 7).days.ago..DateTime.now)
+        Post.undeleted.where(closed: false)
+            .where(locked: false)
+            .where(last_activity: (Rails.env.development? ? 365 : 7).days.ago..DateTime.now)
             .where(post_type_id: [Question.post_type_id, Article.post_type_id])
             .joins(:category).where(categories: { use_for_hot_posts: true })
             .where('score >= ?', SiteSetting['HotPostsScoreThreshold'])
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index f9e0e26c736813820eb11a1492cfcafb1fcf4e96..d3295ea7729f0c1aec73d4ad2b55e99031e2be46 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -99,6 +99,7 @@ class PostsController < ApplicationController
     end
 
     if @post.save
+      @post.update(last_activity: @post.created_at, last_activity_by: current_user)
       if @post_type.has_parent?
         unless @post.user_id == @post.parent.user_id
           @post.parent.user.create_notification("New response to your post #{@post.parent.title}",
diff --git a/app/models/post.rb b/app/models/post.rb
index cac04dbc84ad70deb78bd863f15afbe8e07b9058..458b91df77669d4f2798a1c314f407d5a6c880cf 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -265,9 +265,9 @@ class Post < ApplicationRecord
 
   def maximum_tags
     if tags_cache.length > 5
-      errors.add(:tags, "can't have more than 5 tags")
+      errors.add(:base, "Post can't have more than 5 tags.")
     elsif tags_cache.empty?
-      errors.add(:tags, 'must have at least one tag')
+      errors.add(:base, 'Post must have at least one tag.')
     end
   end
 
diff --git a/app/models/post_flag_type.rb b/app/models/post_flag_type.rb
index 4731ee12bf69f5c046e4b8feedd5563241e210e9..00bed5ce46cf9ccb5a5c751d58bff73d32a61eda 100644
--- a/app/models/post_flag_type.rb
+++ b/app/models/post_flag_type.rb
@@ -1,6 +1,8 @@
 class PostFlagType < ApplicationRecord
   include CommunityRelated
 
+  belongs_to :post_type, optional: true
+
   validates :name, uniqueness: { scope: [:community_id], case_sensitive: false }
 
   scope :not_confidential, -> { where(confidential: false) }
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 06ab403381b00391f412c48da1e5c0f9b07a43f5..3586b8d5134d4a77bb0f3c2d3726bd809e9e480a 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -24,7 +24,10 @@ Rails.application.configure do
   redis_config = YAML.safe_load(processed, permitted_classes: [], permitted_symbols: [], aliases: true)["redis_#{Rails.env}"]
   config.cache_store = QPixel::NamespacedEnvCache.new(
     ActiveSupport::Cache::RedisCacheStore.new(
-      url: "redis://#{redis_config['host']}:#{redis_config['port']}"
+      **redis_config.deep_symbolize_keys.merge(reconnect_attempts: 3),
+      error_handler: -> (method:, returning:, exception:) {
+        Rails.logger.error("Cache error: method=#{method} returning=#{returning} exception=#{exception.message}")
+      }
     )
   )
 
diff --git a/config/environments/production.rb b/config/environments/production.rb
index f86b8ec2fc68b95c52eee9c337ff352d0151458e..7a7b6d2d4a42de6a8afcc240e99c9e218c257c81 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -58,9 +58,16 @@ Rails.application.configure do
   # Prepend all log lines with the following tags.
   config.log_tags = [ :subdomain, :uuid ]
 
-  # Use a different cache store in production.
+  # Set the cache store to the redis that was configured in the database.yml
+  processed = ERB.new(File.read(Rails.root.join('config', 'database.yml'))).result(binding)
+  redis_config = YAML.safe_load(processed, permitted_classes: [], permitted_symbols: [], aliases: true)["redis_#{Rails.env}"]
   config.cache_store = QPixel::NamespacedEnvCache.new(
-    ActiveSupport::Cache::RedisCacheStore.new(url: 'redis://localhost:6379/1')
+    ActiveSupport::Cache::RedisCacheStore.new(
+      **redis_config.deep_symbolize_keys.merge(reconnect_attempts: 3),
+      error_handler: -> (method:, returning:, exception:) {
+        Rails.logger.error("Cache error: method=#{method} returning=#{returning} exception=#{exception.message}")
+      }
+    )
   )
 
   # Use a real queuing backend for Active Job (and separate queues per environment).
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 4c5ef5309711a0e4c6fb2a5213cebb2c07ac63b9..8bd2cfad8a20dcb3ba63cd1efd3196a7fb39695c 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -28,11 +28,15 @@ Rails.application.configure do
   config.consider_all_requests_local       = true
   config.action_controller.perform_caching = false
 
+  # Set the cache store to the redis that was configured in the database.yml
   processed = ERB.new(File.read(Rails.root.join('config', 'database.yml'))).result(binding)
   redis_config = YAML.safe_load(processed, permitted_classes: [], permitted_symbols: [], aliases: true)["redis_#{Rails.env}"]
   config.cache_store = QPixel::NamespacedEnvCache.new(
     ActiveSupport::Cache::RedisCacheStore.new(
-      url: "redis://#{redis_config['host']}:#{redis_config['port']}"
+      **redis_config.deep_symbolize_keys.merge(reconnect_attempts: 3),
+      error_handler: -> (method:, returning:, exception:) {
+        Rails.logger.error("Cache error: method=#{method} returning=#{returning} exception=#{exception.message}")
+      }
     )
   )
 
diff --git a/db/seeds.rb b/db/seeds.rb
index 9a5c8579049a3d7cf5b7df5145725dde12981335..c116c94c36aaaca729481cf0a52ed2b020d8fff9 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -62,7 +62,27 @@ sorted.each do |f, type|
                else
                  # otherwise, no need to worry, just create it
                  [seed]
-               end
+                end
+
+        # Transform all _id relations into the actual rails objects to pass validations
+        seeds = seeds.map do |seed|
+          columns = type.column_names.select { |name| name.match(/^.*_id$/) }
+          new_seed = seed.deep_symbolize_keys
+          columns.each do |column|
+            begin
+              column_type_name = column.chomp('_id')
+              column_type = column_type_name.classify.constantize
+              new_seed = new_seed.except(column.to_sym)
+                                 .merge(column_type_name.to_sym => column_type.unscoped.find(seed[column.to_sym]))
+            rescue StandardError
+              # Either the type does not exist or the value specified as the id is not valid, ignore.
+              next
+            end
+          end
+          new_seed
+        end
+
+        # Actually create the objects and count successes
         objs = type.create seeds
         skipped += objs.select { |o| o.errors.any? }.size
         created += objs.select { |o| !o.errors.any? }.size
diff --git a/db/seeds/categories.yml b/db/seeds/categories.yml
index 9c4387cd57838894d178b822d780e0f80ceaa093..d8c5a1f2876dbd90c5a2269e1e08e4e376991f50 100644
--- a/db/seeds/categories.yml
+++ b/db/seeds/categories.yml
@@ -9,6 +9,7 @@
   tag_set_id: <%= TagSet.unscoped.where(name: 'Main').first.id %>
   use_for_hot_posts: true
   use_for_advertisement: true
+  license_id: <%= License.unscoped.first.id %>
 
 - name: Meta
   short_wiki: Discussions and feedback about the site itself in Q&A format.
@@ -20,4 +21,5 @@
   tag_set_id: <%= TagSet.unscoped.where(name: 'Meta').first.id %>
   use_for_hot_posts: true
   use_for_advertisement: false
-  color_code: bluegray
\ No newline at end of file
+  color_code: bluegray
+  license_id: <%= License.unscoped.first.id %>
\ No newline at end of file