diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37b8b0be7..c133cd9ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -84,6 +84,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - ActivityPub: Configurable `type` field of the actors.
 - Mastodon API: `/api/v1/accounts/:id` has `source/pleroma/actor_type` field.
 - Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field.
+- Captcha: Support native provider
+- Captcha: Enable by default
 </details>
 
 ### Fixed
diff --git a/config/config.exs b/config/config.exs
index 6ed800056..370ddd855 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -66,9 +66,9 @@
   jobs: scheduled_jobs
 
 config :pleroma, Pleroma.Captcha,
-  enabled: false,
+  enabled: true,
   seconds_valid: 60,
-  method: Pleroma.Captcha.Kocaptcha
+  method: Pleroma.Captcha.Native
 
 config :pleroma, :hackney_pools,
   federation: [
@@ -84,8 +84,6 @@
     timeout: 300_000
   ]
 
-config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
-
 # Upload configuration
 config :pleroma, Pleroma.Upload,
   uploader: Pleroma.Uploaders.Local,
diff --git a/config/test.exs b/config/test.exs
index d52ced612..b48b89c8f 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -95,6 +95,8 @@
 
 config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
 
+config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
+
 if File.exists?("./config/test.secret.exs") do
   import_config "test.secret.exs"
 else
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index ef2711e3c..ce2a14210 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -379,13 +379,19 @@ For each pool, the options are:
 ## Captcha
 
 ### Pleroma.Captcha
+
 * `enabled`: Whether the captcha should be shown on registration.
 * `method`: The method/service to use for captcha.
 * `seconds_valid`: The time in seconds for which the captcha is valid.
 
 ### Captcha providers
 
+#### Pleroma.Captcha.Native
+
+A built-in captcha provider. Enabled by default.
+
 #### Pleroma.Captcha.Kocaptcha
+
 Kocaptcha is a very simple captcha service with a single API endpoint,
 the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint
 `https://captcha.kotobank.ch` is hosted by the developer.
diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex
new file mode 100644
index 000000000..5306fe1aa
--- /dev/null
+++ b/lib/pleroma/captcha/native.ex
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Captcha.Native do
+  import Pleroma.Web.Gettext
+  alias Pleroma.Captcha.Service
+  @behaviour Service
+
+  @impl Service
+  def new do
+    case Captcha.get() do
+      {:timeout} ->
+        %{error: dgettext("errors", "Captcha timeout")}
+
+      {:ok, answer_data, img_binary} ->
+        %{
+          type: :native,
+          token: token(),
+          url: "data:image/png;base64," <> Base.encode64(img_binary),
+          answer_data: answer_data
+        }
+    end
+  end
+
+  @impl Service
+  def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok
+  def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")}
+
+  defp token do
+    10
+    |> :crypto.strong_rand_bytes()
+    |> Base.url_encode64(padding: false)
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 4ea37fc7b..4073d3d63 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
   alias Pleroma.HTTP
   alias Pleroma.Instances
   alias Pleroma.Object
+  alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -188,31 +189,35 @@ def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
 
     recipients = recipients(actor, activity)
 
-    recipients
-    |> Enum.filter(&User.ap_enabled?/1)
-    |> Enum.map(fn %{source_data: data} -> data["inbox"] end)
-    |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
-    |> Instances.filter_reachable()
-    |> Enum.each(fn {inbox, unreachable_since} ->
-      %User{ap_id: ap_id} =
-        Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end)
+    inboxes =
+      recipients
+      |> Enum.filter(&User.ap_enabled?/1)
+      |> Enum.map(fn %{source_data: data} -> data["inbox"] end)
+      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+      |> Instances.filter_reachable()
 
-      # Get all the recipients on the same host and add them to cc. Otherwise, a remote
-      # instance would only accept a first message for the first recipient and ignore the rest.
-      cc = get_cc_ap_ids(ap_id, recipients)
+    Repo.checkout(fn ->
+      Enum.each(inboxes, fn {inbox, unreachable_since} ->
+        %User{ap_id: ap_id} =
+          Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end)
 
-      json =
-        data
-        |> Map.put("cc", cc)
-        |> Jason.encode!()
+        # Get all the recipients on the same host and add them to cc. Otherwise, a remote
+        # instance would only accept a first message for the first recipient and ignore the rest.
+        cc = get_cc_ap_ids(ap_id, recipients)
 
-      Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
-        inbox: inbox,
-        json: json,
-        actor_id: actor.id,
-        id: activity.data["id"],
-        unreachable_since: unreachable_since
-      })
+        json =
+          data
+          |> Map.put("cc", cc)
+          |> Jason.encode!()
+
+        Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
+          inbox: inbox,
+          json: json,
+          actor_id: actor.id,
+          id: activity.data["id"],
+          unreachable_since: unreachable_since
+        })
+      end)
     end)
   end
 
diff --git a/mix.exs b/mix.exs
index 7c8e52a67..f1a62f6e3 100644
--- a/mix.exs
+++ b/mix.exs
@@ -163,6 +163,9 @@ defp deps do
       {:remote_ip,
        git: "https://git.pleroma.social/pleroma/remote_ip.git",
        ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"},
+      {:captcha,
+       git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
+       ref: "c3c795c55f6b49d79d6ac70a0f91e525099fc3e2"},
       {:mox, "~> 0.5", only: :test}
     ] ++ oauth_deps()
   end
diff --git a/mix.lock b/mix.lock
index b22918156..401bcdb6e 100644
--- a/mix.lock
+++ b/mix.lock
@@ -8,6 +8,7 @@
   "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
   "cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
   "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+  "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "c3c795c55f6b49d79d6ac70a0f91e525099fc3e2", [ref: "c3c795c55f6b49d79d6ac70a0f91e525099fc3e2"]},
   "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
   "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
diff --git a/priv/static/static/font/fontello.1576166651574.svg b/priv/static/static/font/fontello.1576166651574.svg
old mode 100644
new mode 100755
diff --git a/test/captcha_test.exs b/test/captcha_test.exs
index 9f395d6b4..393c8219e 100644
--- a/test/captcha_test.exs
+++ b/test/captcha_test.exs
@@ -8,6 +8,7 @@ defmodule Pleroma.CaptchaTest do
   import Tesla.Mock
 
   alias Pleroma.Captcha.Kocaptcha
+  alias Pleroma.Captcha.Native
 
   @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}]
 
@@ -43,4 +44,21 @@ test "new and validate" do
              ) == :ok
     end
   end
+
+  describe "Native" do
+    test "new and validate" do
+      new = Native.new()
+
+      assert %{
+               answer_data: answer,
+               token: token,
+               type: :native,
+               url: "data:image/png;base64," <> _
+             } = new
+
+      assert is_binary(answer)
+      assert :ok = Native.validate(token, answer, answer)
+      assert {:error, "Invalid CAPTCHA"} == Native.validate(token, answer, answer <> "foobar")
+    end
+  end
 end