diff --git a/app/Console/Commands/CatchUnoptimizedMedia.php b/app/Console/Commands/CatchUnoptimizedMedia.php
index 6b3c3547d97fe238feac9932cc5bb4774f59457e..9038c12fba902f9931c9469071c5dd2938703d4c 100644
--- a/app/Console/Commands/CatchUnoptimizedMedia.php
+++ b/app/Console/Commands/CatchUnoptimizedMedia.php
@@ -42,9 +42,11 @@ class CatchUnoptimizedMedia extends Command
     {
         DB::transaction(function() {
             Media::whereNull('processed_at')
+                ->where('skip_optimize', '!=', true)
                 ->whereNull('remote_url')
                 ->whereNotNull('status_id')
                 ->whereNotNull('media_path')
+                ->where('created_at', '>', now()->subHours(1))
                 ->whereIn('mime', [
                     'image/jpeg',
                     'image/png',
diff --git a/app/Console/Commands/FixSoftDeletedProfile.php b/app/Console/Commands/FixSoftDeletedProfile.php
deleted file mode 100644
index 3fe90ff3cec8b199b4a15acfdbb04ca4609576c4..0000000000000000000000000000000000000000
--- a/app/Console/Commands/FixSoftDeletedProfile.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use Illuminate\Console\Command;
-use App\Profile;
-use App\User;
-
-class FixSoftDeletedProfile extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'fix:sdprofile';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = 'Command description';
-
-    /**
-     * Create a new command instance.
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        parent::__construct();
-    }
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        $profiles = Profile::whereNull('domain')
-            ->withTrashed()
-            ->where('deleted_at', '>', now()->subDays(14))
-            ->whereNull('status')
-            ->pluck('username');
-
-        if($profiles->count() == 0) {
-            return 0;
-        }
-
-        foreach($profiles as $p) {
-            if(User::whereUsername($p)->first()->status == null) {
-                $pro = Profile::withTrashed()->whereUsername($p)->firstOrFail();
-                $pro->deleted_at = null;
-                $pro->save();
-            }
-        }
-    }
-}
diff --git a/app/Console/Commands/GenerateInstanceActor.php b/app/Console/Commands/GenerateInstanceActor.php
new file mode 100644
index 0000000000000000000000000000000000000000..c70b13a12a5b0509739532f47e7b09f20c4e2269
--- /dev/null
+++ b/app/Console/Commands/GenerateInstanceActor.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Facades\DB;
+use App\Models\InstanceActor;
+use Cache;
+
+class GenerateInstanceActor extends Command
+{
+	protected $signature = 'instance:actor';
+	protected $description = 'Generate instance actor';
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function handle()
+	{
+		if(Schema::hasTable('instance_actors') == false) {
+			$this->line(' ');
+			$this->error('Missing instance_actors table.');
+			$this->info('Run "php artisan migrate" and try again.');
+			$this->line(' ');
+			exit;
+		}
+
+		if(InstanceActor::exists()) {
+			$this->line(' ');
+			$this->error('Instance actor already exists!');
+			$this->line(' ');
+			$actor = InstanceActor::whereNotNull('public_key')
+				->whereNotNull('private_key')
+				->firstOrFail();
+			Cache::rememberForever(InstanceActor::PKI_PUBLIC, function() use($actor) {
+				return $actor->public_key;
+			});
+
+			Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) {
+				return $actor->private_key;
+			});
+			exit;
+		}
+
+		$pkiConfig = [
+			'digest_alg'       => 'sha512',
+			'private_key_bits' => 2048,
+			'private_key_type' => OPENSSL_KEYTYPE_RSA,
+		];
+		$pki = openssl_pkey_new($pkiConfig);
+		openssl_pkey_export($pki, $pki_private);
+		$pki_public = openssl_pkey_get_details($pki);
+		$pki_public = $pki_public['key'];
+
+		$actor = new InstanceActor();
+		$actor->public_key = $pki_public;
+		$actor->private_key = $pki_private;
+		$actor->save();
+
+		Cache::rememberForever(InstanceActor::PKI_PUBLIC, function() use($actor) {
+			return $actor->public_key;
+		});
+
+		Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) {
+			return $actor->private_key;
+		});
+
+		$this->info('Instance actor succesfully generated. You do not need to run this command again.');
+
+		return 0;
+	}
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index d5e1d47a46e0330ab18f914adf29ddc15ae1e728..0dd65888ab3d5a5bdd13afdb5fb5d3c703f361c1 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -25,13 +25,10 @@ class Kernel extends ConsoleKernel
      */
     protected function schedule(Schedule $schedule)
     {
-        $schedule->command('media:optimize')
-                 ->hourly();
-        $schedule->command('media:gc')
-                 ->hourly();
+        $schedule->command('media:optimize')->hourly();
+        $schedule->command('media:gc')->hourly();
         $schedule->command('horizon:snapshot')->everyFiveMinutes();
         $schedule->command('story:gc')->everyFiveMinutes();
-        $schedule->command('fix:sdprofile')->everyFiveMinutes();
         $schedule->command('gc:failedjobs')->dailyAt(3);
         $schedule->command('gc:passwordreset')->dailyAt('09:41');
     }
diff --git a/app/Http/Controllers/InstanceActorController.php b/app/Http/Controllers/InstanceActorController.php
new file mode 100644
index 0000000000000000000000000000000000000000..136a2ede5dcbac98fe37db4f74159d515712ec42
--- /dev/null
+++ b/app/Http/Controllers/InstanceActorController.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Models\InstanceActor;
+use Cache;
+
+class InstanceActorController extends Controller
+{
+	public function profile()
+	{
+		$res = Cache::rememberForever(InstanceActor::PROFILE_KEY, function() {
+			$res = (new InstanceActor())->first()->getActor();
+			return json_encode($res);
+		});
+		return response($res)->header('Content-Type', 'application/json');
+	}
+
+	public function inbox()
+	{
+		return;
+	}
+
+	public function outbox()
+	{
+		$res = [
+			'@context' => 'https://www.w3.org/ns/activitystreams',
+			'id' => config('app.url') . '/i/actor/outbox',
+			'type' => 'OrderedCollection',
+			'totalItems' => 0,
+			'first' => config('app.url') . '/i/actor/outbox?page=true',
+			'last' =>  config('app.url') . '/i/actor/outbox?min_id=0page=true'
+		];
+		return response()->json($res);
+	}
+}
diff --git a/app/Models/InstanceActor.php b/app/Models/InstanceActor.php
new file mode 100644
index 0000000000000000000000000000000000000000..fad1fc86bfd2883be72d3ed5bd1485f66aff70a7
--- /dev/null
+++ b/app/Models/InstanceActor.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class InstanceActor extends Model
+{
+	use HasFactory;
+
+	const PROFILE_BASE = '/i/actor';
+	const KEY_ID = '/i/actor#main-key';
+	const PROFILE_KEY = 'federation:_v2:instance:actor:profile';
+	const PKI_PUBLIC = 'federation:_v1:instance:actor:profile:pki_public';
+	const PKI_PRIVATE = 'federation:_v1:instance:actor:profile:pki_private';
+
+	public function permalink($suffix = '')
+	{
+		return url(self::PROFILE_BASE . $suffix);
+	}
+
+	public function getActor()
+	{
+		return [
+			'@context' => 'https://www.w3.org/ns/activitystreams',
+			'id' => $this->permalink(),
+			'type' => 'Application',
+			'inbox' => $this->permalink('/inbox'),
+			'outbox' => $this->permalink('/outbox'),
+			'preferredUsername' => config('pixelfed.domain.app'),
+			'publicKey' => [
+				'id' => $this->permalink('#main-key'),
+				'owner' => $this->permalink(),
+				'publicKeyPem' => $this->public_key
+			],
+			'manuallyApprovesFollowers' => true,
+			'url' => route('help.instance-actor')
+		];
+	}
+}
diff --git a/app/Util/ActivityPub/HttpSignature.php b/app/Util/ActivityPub/HttpSignature.php
index d6ed0040ba2eeaf13ec1e7632396a2be091e546a..516979f5cba35545fbd05a2e7633348320e1b388 100644
--- a/app/Util/ActivityPub/HttpSignature.php
+++ b/app/Util/ActivityPub/HttpSignature.php
@@ -2,7 +2,8 @@
 
 namespace App\Util\ActivityPub;
 
-use Log;
+use Cache, Log;
+use App\Models\InstanceActor;
 use App\Profile;
 use \DateTime;
 
@@ -32,6 +33,29 @@ class HttpSignature {
     return self::_headersToCurlArray($headers);
   }
 
+  public static function instanceActorSign($url, $body = false, $addlHeaders = [])
+  {
+    $keyId = config('app.url') . '/i/actor#main-key';
+    $privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() {
+      return InstanceActor::first()->private_key;
+    });
+    if($body) {
+      $digest = self::_digest($body);
+    }
+    $headers = self::_headersToSign($url, $body ? $digest : false);
+    $headers = array_merge($headers, $addlHeaders);
+    $stringToSign = self::_headersToSigningString($headers);
+    $signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
+    $key = openssl_pkey_get_private($privateKey);
+    openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
+    $signature = base64_encode($signature);
+    $signatureHeader = 'keyId="'.$keyId.'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
+    unset($headers['(request-target)']);
+    $headers['Signature'] = $signatureHeader;
+
+    return self::_headersToCurlArray($headers);
+  }
+
   public static function parseSignatureHeader($signature) {
     $parts = explode(',', $signature);
     $signatureData = [];
diff --git a/app/Util/Lexer/RestrictedNames.php b/app/Util/Lexer/RestrictedNames.php
index e104b31d2ad26cd96cfb14879c22f8808ad3f828..09af01bfaca695bc87f8d3541e71a4b262117d00 100644
--- a/app/Util/Lexer/RestrictedNames.php
+++ b/app/Util/Lexer/RestrictedNames.php
@@ -98,6 +98,8 @@ class RestrictedNames
 		'aboutus',
 		'about-us',
 		'abuse',
+		'actor',
+		'actors',
 		'account',
 		'admins',
 		'api',
@@ -179,6 +181,7 @@ class RestrictedNames
 		'help-center_',
 		'help_center-',
 		'i',
+		'instance',
 		'inbox',
 		'img',
 		'imgs',
@@ -208,6 +211,17 @@ class RestrictedNames
 		'media',
 		'menu',
 		'music',
+		'my2020',
+		'my2021',
+		'my2022',
+		'my2023',
+		'my2024',
+		'my2025',
+		'my2026',
+		'my2027',
+		'my2028',
+		'my2029',
+		'my2030',
 		'n',
 		'news',
 		'new',
diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php
index b59b610f0f58965d39f3d1fce2bafc8c849cd980..88c211ca98657e7a7c7c8a379bb0fd1b26a9bce2 100644
--- a/app/Util/Site/Config.php
+++ b/app/Util/Site/Config.php
@@ -8,7 +8,7 @@ use Illuminate\Support\Str;
 class Config {
 
 	public static function get() {
-		return Cache::remember('api:site:configuration:_v0', now()->addHours(30), function() {
+		return Cache::remember('api:site:configuration:_v0.1', now()->addHours(30), function() {
 			return [
 				'open_registration' => config('pixelfed.open_registration'),
 				'uploader' => [
@@ -34,7 +34,8 @@ class Config {
 				'ab' => [
 					'lc' => config('exp.lc'),
 					'rec' => config('exp.rec'),
-					'loops' => config('exp.loops')
+					'loops' => config('exp.loops'),
+					'top' => config('exp.top')
 				],
 
 				'site' => [
diff --git a/config/exp.php b/config/exp.php
index 440997614d213a0e3760c5d617ccc0b69b816dd6..74e9a5e49ed88ab5884ab518e5419b61b0921599 100644
--- a/config/exp.php
+++ b/config/exp.php
@@ -3,6 +3,7 @@
 return [
 
 	'lc' => env('EXP_LC', false),
-	'rec' => env('EXP_REC', false),
-	'loops' => env('EXP_LOOPS', false),
+	'rec' => false,
+	'loops' => false,
+	'top' => env('EXP_TOP', false),
 ];
diff --git a/database/migrations/2021_01_15_050602_create_instance_actors_table.php b/database/migrations/2021_01_15_050602_create_instance_actors_table.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f06cd3fbecb2e6570e67d0c39c3a556cbdd234b
--- /dev/null
+++ b/database/migrations/2021_01_15_050602_create_instance_actors_table.php
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateInstanceActorsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('instance_actors', function (Blueprint $table) {
+            $table->id();
+            $table->text('private_key')->nullable();
+            $table->text('public_key')->nullable();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('instance_actors');
+    }
+}