diff --git a/files/gitlab-ctl-commands/check_config.rb b/files/gitlab-ctl-commands/check_config.rb
index b7c86a77785a87fb29bd0f156acc13a65362bf8d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9jaGVja19jb25maWcucmI=..46b2f4b070c618ee1159553bac674604dfcac5cb_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9jaGVja19jb25maWcucmI= 100644
--- a/files/gitlab-ctl-commands/check_config.rb
+++ b/files/gitlab-ctl-commands/check_config.rb
@@ -17,6 +17,7 @@
 
 require "#{base_path}/embedded/cookbooks/package/libraries/deprecations"
 require 'json'
+require_relative "lib/gitlab_ctl/util"
 
 add_command 'check-config', 'Check if there are any configuration in gitlab.rb that is removed in specified version', 2 do |cmd_name|
   # Extracting JSON from the fqdn.json file generated by reconfigure.
@@ -29,6 +30,12 @@
     log "Skipping config check."
     Kernel.exit 0
   end
+
+  if GitlabCtl::Util.public_attributes_broken?
+    log 'Public attributes file is missing, run gitlab-ctl reconfigure to re-create it.'
+    Kernel.exit 1
+  end
+
   node_json = JSON.parse(File.read(node_json_file))
   unless node_json.key?("normal")
     log "Malformed configuration JSON file found at #{node_json_file}."
diff --git a/files/gitlab-ctl-commands/lib/gitlab_ctl/util.rb b/files/gitlab-ctl-commands/lib/gitlab_ctl/util.rb
index b7c86a77785a87fb29bd0f156acc13a65362bf8d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9saWIvZ2l0bGFiX2N0bC91dGlsLnJi..46b2f4b070c618ee1159553bac674604dfcac5cb_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9saWIvZ2l0bGFiX2N0bC91dGlsLnJi 100644
--- a/files/gitlab-ctl-commands/lib/gitlab_ctl/util.rb
+++ b/files/gitlab-ctl-commands/lib/gitlab_ctl/util.rb
@@ -2,8 +2,9 @@
 require 'io/console'
 require 'chef/mash'
 require 'chef/mixins'
+require 'json'
 
 require 'socket'
 
 module GitlabCtl
   module Util
@@ -5,8 +6,10 @@
 
 require 'socket'
 
 module GitlabCtl
   module Util
+    PUBLIC_ATTRIBUTES_FILE = '/var/opt/gitlab/public_attributes.json'.freeze
+
     class <<self
       def get_command_output(command, user = nil, timeout = nil)
         begin
@@ -64,5 +67,5 @@
       end
 
       def get_public_node_attributes
-        attribute_file = '/var/opt/gitlab/public_attributes.json'
+        return {} if public_attributes_missing?
 
@@ -68,7 +71,5 @@
 
-        return {} unless File.exist?(attribute_file)
-
-        parse_json_file(attribute_file)
+        parse_json_file(PUBLIC_ATTRIBUTES_FILE)
       end
 
       def get_password(input_text: 'Enter password: ', do_confirm: true)
@@ -161,6 +162,16 @@
 
         millis.to_i
       end
+
+      def public_attributes_missing?
+        !File.exist?(PUBLIC_ATTRIBUTES_FILE)
+      end
+
+      def public_attributes_broken?(attribute_key = "gitlab")
+        return true if public_attributes_missing?
+
+        !get_public_node_attributes.key?(attribute_key)
+      end
     end
   end
 end
diff --git a/files/gitlab-ctl-commands/upgrade.rb b/files/gitlab-ctl-commands/upgrade.rb
index b7c86a77785a87fb29bd0f156acc13a65362bf8d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy91cGdyYWRlLnJi..46b2f4b070c618ee1159553bac674604dfcac5cb_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy91cGdyYWRlLnJi 100644
--- a/files/gitlab-ctl-commands/upgrade.rb
+++ b/files/gitlab-ctl-commands/upgrade.rb
@@ -42,6 +42,11 @@
     print_welcome_and_exit
   end
 
+  if GitlabCtl::Util.public_attributes_broken?
+    log 'It looks like there was a problem with public attributes; run gitlab-ctl reconfigure manually to fix.'
+    Kernel.exit 1
+  end
+
   unless GitlabCtl::Util.progress_message('Checking PostgreSQL executables') do
     remove_old_node_state
     status = GitlabCtl::Util.chef_run('solo.rb', 'postgresql-bin.json')
diff --git a/spec/gitlab-ctl-commands/lib/util_spec.rb b/spec/gitlab-ctl-commands/lib/util_spec.rb
index b7c86a77785a87fb29bd0f156acc13a65362bf8d_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi91dGlsX3NwZWMucmI=..46b2f4b070c618ee1159553bac674604dfcac5cb_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi91dGlsX3NwZWMucmI= 100644
--- a/spec/gitlab-ctl-commands/lib/util_spec.rb
+++ b/spec/gitlab-ctl-commands/lib/util_spec.rb
@@ -206,6 +206,48 @@
     end
   end
 
+  describe "#public_attributes_missing?" do
+    context 'when the public attributes file is missing' do
+      before do
+        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(false)
+      end
+
+      it 'returns true' do
+        expect(GitlabCtl::Util.public_attributes_missing?).to be true
+      end
+    end
+  end
+
+  describe "#public_attributes_broken?" do
+    context 'when the public attributes file is broken' do
+      before do
+        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(true)
+      end
+
+      it 'returns true when the attribute is missing' do
+        allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({})
+        expect(GitlabCtl::Util.public_attributes_broken?).to be true
+      end
+
+      it 'returns false by default when the gitlab attribute is found' do
+        allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ "gitlab" => "hithere" })
+        expect(GitlabCtl::Util.public_attributes_broken?).to be false
+      end
+
+      context 'when using an alternate key to check for broken' do
+        it 'returns true when the key is missing' do
+          allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({})
+          expect(GitlabCtl::Util.public_attributes_broken?('funky')).to be true
+        end
+
+        it 'returns false when the key is present' do
+          allow(GitlabCtl::Util).to receive(:get_public_node_attributes).and_return({ "funky" => "llama" })
+          expect(GitlabCtl::Util.public_attributes_broken?('funky')).to be false
+        end
+      end
+    end
+  end
+
   describe '#get_public_node_attributes' do
     context 'when node file is missing' do
       before do
@@ -209,7 +251,7 @@
   describe '#get_public_node_attributes' do
     context 'when node file is missing' do
       before do
-        allow(File).to receive(:exist?).with(%r{/var/opt/gitlab/public_attributes.json}).and_return(false)
+        allow(GitlabCtl::Util).to receive(:public_attributes_missing?).and_return(true)
       end
 
       it 'returns empty hash' do