diff --git a/changelogs/unreleased/36860-migrate-issues-author.yml b/changelogs/unreleased/36860-migrate-issues-author.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e9fcc558365f381e8cb1087d58746afeb691fb0
--- /dev/null
+++ b/changelogs/unreleased/36860-migrate-issues-author.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate issues authored by deleted user to the Ghost user
+merge_request:
+author:
+type: fixed
diff --git a/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb b/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb
new file mode 100644
index 0000000000000000000000000000000000000000..294141e4fdb8d6e42b5215464e7a7e192caf561b
--- /dev/null
+++ b/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb
@@ -0,0 +1,36 @@
+class MigrateIssuesToGhostUser < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  class User < ActiveRecord::Base
+    self.table_name = 'users'
+  end
+
+  class Issue < ActiveRecord::Base
+    self.table_name = 'issues'
+
+    include ::EachBatch
+  end
+
+  def reset_column_in_migration_models
+    ActiveRecord::Base.clear_cache!
+
+    ::User.reset_column_information
+  end
+
+  def up
+    reset_column_in_migration_models
+
+    # we use the model method because rewriting it is too complicated and would require copying multiple methods
+    ghost_id = ::User.ghost.id
+
+    Issue.where('NOT EXISTS (?)', User.unscoped.select(1).where('issues.author_id = users.id')).each_batch do |relation|
+      relation.update_all(author_id: ghost_id)
+    end
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20170901071411_add_foreign_key_to_issue_author.rb b/db/migrate/20170901071411_add_foreign_key_to_issue_author.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab6e9fb565af9049e5c1a21c6e98feaf69bd41d8
--- /dev/null
+++ b/db/migrate/20170901071411_add_foreign_key_to_issue_author.rb
@@ -0,0 +1,14 @@
+class AddForeignKeyToIssueAuthor < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key(:issues, :users, column: :author_id, on_delete: :nullify)
+  end
+
+  def down
+    remove_foreign_key(:issues, column: :author_id)
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 35e99e75caa844a3ae1fc5066dde59f0438e1d88..b6503be21b66e1709f82957daa77a1ed3eb100b2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20170824162758) do
+ActiveRecord::Schema.define(version: 20170901071411) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -2072,6 +2072,7 @@
   add_foreign_key "issue_links", "issues", column: "target_id", name: "fk_e71bb44f1f", on_delete: :cascade
   add_foreign_key "issue_metrics", "issues", on_delete: :cascade
   add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade
+  add_foreign_key "issues", "users", column: "author_id", name: "fk_05f1e72feb", on_delete: :cascade
   add_foreign_key "label_priorities", "labels", on_delete: :cascade
   add_foreign_key "label_priorities", "projects", on_delete: :cascade
   add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cfd4021fbac837372af04cb5429aaa1c13a503f2
--- /dev/null
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170825104051_migrate_issues_to_ghost_user.rb')
+
+describe MigrateIssuesToGhostUser, :migration do
+  describe '#up' do
+    let(:projects) { table(:projects) }
+    let(:issues) { table(:issues) }
+    let(:users) { table(:users) }
+
+    before do
+      projects.create!(name: 'gitlab')
+      user = users.create(email: 'test@example.com')
+      issues.create(title: 'Issue 1', author_id: nil, project_id: 1)
+      issues.create(title: 'Issue 2', author_id: user.id, project_id: 1)
+    end
+
+    context 'when ghost user exists' do
+      let!(:ghost) { users.create(ghost: true, email: 'ghost@example.com') }
+
+      it 'does not create a new user' do
+        expect { schema_migrate_up! }.not_to change { User.count }
+      end
+
+      it 'migrates issues where author = nil to the ghost user' do
+        schema_migrate_up!
+
+        expect(issues.first.reload.author_id).to eq(ghost.id)
+      end
+
+      it 'does not change issues authored by an existing user' do
+        expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+      end
+    end
+
+    context 'when ghost user does not exist' do
+      it 'creates a new user' do
+        expect { schema_migrate_up! }.to change { User.count }.by(1)
+      end
+
+      it 'migrates issues where author = nil to the ghost user' do
+        schema_migrate_up!
+
+        expect(issues.first.reload.author_id).to eq(User.ghost.id)
+      end
+
+      it 'does not change issues authored by an existing user' do
+        expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+      end
+    end
+  end
+end
diff --git a/spec/services/issues/export_csv_service_spec.rb b/spec/services/issues/export_csv_service_spec.rb
index 366701b44aab3d40bd66a4eeef5015f2a02805b1..e79983e126663e6700139b060ed3bc39de5ae48e 100644
--- a/spec/services/issues/export_csv_service_spec.rb
+++ b/spec/services/issues/export_csv_service_spec.rb
@@ -134,12 +134,4 @@ def csv
       expect(csv[0]['Labels']).to eq nil
     end
   end
-
-  it 'succeeds when author is non-existent' do
-    issue.author_id = 99999999
-    issue.save(validate: false)
-
-    expect(csv[0]['Author']).to eq nil
-    expect(csv[0]['Author Username']).to eq nil
-  end
 end