diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index cc1db34c095a5755d53e412425e52ff353985710..8156fd56d602cbf0e15706380570e18e24244385 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -77,6 +77,7 @@ set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${lua_sources})
 include_directories(${ZSTD_INCLUDE_DIRS})
 include_directories(${PROJECT_BINARY_DIR}/src/box/sql)
 include_directories(${PROJECT_BINARY_DIR}/src/box)
+include_directories(${EXTRA_CORE_INCLUDE_DIRS})
 include_directories(${EXTRA_BOX_INCLUDE_DIRS})
 
 add_library(box_error STATIC error.cc errcode.c mp_error.cc)
diff --git a/src/box/mp_error.cc b/src/box/mp_error.cc
index fba562a84afd3843af0d92802f291ed15f48d656..3c4176e591967dea597a702763ee0a52ef44d5ec 100644
--- a/src/box/mp_error.cc
+++ b/src/box/mp_error.cc
@@ -41,6 +41,7 @@
 #include "msgpuck.h"
 #include "mp_extension_types.h"
 #include "fiber.h"
+#include "ssl_error.h"
 
 /**
  * MP_ERROR format:
@@ -257,6 +258,8 @@ error_build_xc(struct mp_error *mp_error)
 		err = new SwimError();
 	} else if (strcmp(mp_error->type, "CryptoError") == 0) {
 		err = new CryptoError();
+	} else if (strcmp(mp_error->type, "SSLError") == 0) {
+		err = new SSLError();
 	} else {
 		err = new ClientError();
 	}
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 220a39e683581655b9212ea08f6fb97c9612a2da..0f76833c4baa728ebf9e34b7c092f98730e36f63 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -37,6 +37,18 @@ set(core_sources
     mp_datetime.c
 )
 
+if(ENABLE_SSL)
+    list(APPEND core_sources ${SSL_SOURCES})
+else()
+    list(APPEND core_sources ssl.c ssl_error.cc)
+endif()
+
+include_directories(${EXTRA_CORE_INCLUDE_DIRS})
+
+if(ENABLE_SSL)
+    include_directories(${OPENSSL_INCLUDE_DIR})
+endif()
+
 if (TARGET_OS_NETBSD)
     # A workaround for "undefined reference to `__gcc_personality_v0'"
     # on x86_64-rumprun-netbsd-gcc
@@ -63,3 +75,7 @@ endif()
 if ("${HAVE_CLOCK_GETTIME}" AND NOT "${HAVE_CLOCK_GETTIME_WITHOUT_RT}")
     target_link_libraries(core rt)
 endif()
+
+if(ENABLE_SSL)
+    target_link_libraries(core ${OPENSSL_LIBRARIES})
+endif()
diff --git a/src/lib/core/iostream.c b/src/lib/core/iostream.c
index 17b7c85eb4b2baf39463747630d52db8a517148b..474fd7ec0f34aa555199652d232733ea2e967930 100644
--- a/src/lib/core/iostream.c
+++ b/src/lib/core/iostream.c
@@ -12,7 +12,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "diag.h"
 #include "sio.h"
+#include "ssl.h"
+#include "uri/uri.h"
 
 static const struct iostream_vtab plain_iostream_vtab;
 
@@ -86,23 +89,48 @@ int
 iostream_ctx_create(struct iostream_ctx *ctx, enum iostream_mode mode,
 		    const struct uri *uri)
 {
-	(void)uri;
 	assert(mode == IOSTREAM_SERVER || mode == IOSTREAM_CLIENT);
 	ctx->mode = mode;
+	const char *transport = uri_param(uri, "transport", 0);
+	if (transport != NULL) {
+		if (strcmp(transport, "ssl") == 0) {
+			ctx->ssl = ssl_iostream_ctx_new(mode, uri);
+			if (ctx->ssl == NULL)
+				goto err;
+		} else if (strcmp(transport, "plain") == 0) {
+			ctx->ssl = NULL;
+		} else {
+			diag_set(IllegalParams, "Invalid transport: %s",
+				 transport);
+			goto err;
+		}
+	}
 	return 0;
+err:
+	iostream_ctx_clear(ctx);
+	return -1;
 }
 
 void
 iostream_ctx_destroy(struct iostream_ctx *ctx)
 {
+	if (ctx->ssl != NULL)
+		ssl_iostream_ctx_delete(ctx->ssl);
 	iostream_ctx_clear(ctx);
 }
 
 int
-iostream_create(struct iostream *io, int fd, struct iostream_ctx *ctx)
+iostream_create(struct iostream *io, int fd, const struct iostream_ctx *ctx)
 {
 	assert(ctx->mode == IOSTREAM_SERVER || ctx->mode == IOSTREAM_CLIENT);
-	(void)ctx;
-	plain_iostream_create(io, fd);
+	if (ctx->ssl != NULL) {
+		if (ssl_iostream_create(io, fd, ctx->mode, ctx->ssl) != 0)
+			goto err;
+	} else {
+		plain_iostream_create(io, fd);
+	}
 	return 0;
+err:
+	iostream_clear(io);
+	return -1;
 }
diff --git a/src/lib/core/iostream.h b/src/lib/core/iostream.h
index 9e25fd1386ba197b5c90107f972ee9a46f371fd5..ec4780047e09fdfe70daca95edbaba65dc3dd27b 100644
--- a/src/lib/core/iostream.h
+++ b/src/lib/core/iostream.h
@@ -191,12 +191,19 @@ enum iostream_mode {
 	IOSTREAM_CLIENT,
 };
 
+struct ssl_iostream_ctx;
+
 /**
  * Context used for creating IO stream objects of a particular type.
  */
 struct iostream_ctx {
 	/** IO stream mode: server or client. */
 	enum iostream_mode mode;
+	/**
+	 * Context used for creating encrypted streams. If it's NULL, then
+	 * streams created with this context will be unencrypted.
+	 */
+	struct ssl_iostream_ctx *ssl;
 };
 
 /**
@@ -208,6 +215,7 @@ static inline void
 iostream_ctx_clear(struct iostream_ctx *ctx)
 {
 	ctx->mode = IOSTREAM_MODE_UNINITIALIZED;
+	ctx->ssl = NULL;
 }
 
 /**
@@ -232,7 +240,7 @@ iostream_ctx_destroy(struct iostream_ctx *ctx);
  * and clears the iostream struct (see iostream_clear).
  */
 int
-iostream_create(struct iostream *io, int fd, struct iostream_ctx *ctx);
+iostream_create(struct iostream *io, int fd, const struct iostream_ctx *ctx);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/lib/core/ssl.c b/src/lib/core/ssl.c
new file mode 100644
index 0000000000000000000000000000000000000000..ae456ade9f353bd595829a4f7e73cb1560c36b90
--- /dev/null
+++ b/src/lib/core/ssl.c
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#include "ssl.h"
+
+#include <stddef.h>
+
+#include "diag.h"
+#include "iostream.h"
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# error unimplemented
+#endif
+
+struct ssl_iostream_ctx *
+ssl_iostream_ctx_new(enum iostream_mode mode, const struct uri *uri)
+{
+	(void)mode;
+	(void)uri;
+	diag_set(IllegalParams, "SSL is not available in this build");
+	return NULL;
+}
diff --git a/src/lib/core/ssl.h b/src/lib/core/ssl.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c0dabba224245a4c7f5c8a5f2dba855c202d912
--- /dev/null
+++ b/src/lib/core/ssl.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#pragma once
+
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# include "ssl_impl.h"
+#else /* !defined(ENABLE_SSL) */
+
+#include "iostream.h"
+#include "trivia/util.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct uri;
+struct ssl_iostream_ctx;
+
+struct ssl_iostream_ctx *
+ssl_iostream_ctx_new(enum iostream_mode mode, const struct uri *uri);
+
+static inline void
+ssl_iostream_ctx_delete(struct ssl_iostream_ctx *ctx)
+{
+	(void)ctx;
+	unreachable();
+}
+
+static inline int
+ssl_iostream_create(struct iostream *io, int fd, enum iostream_mode mode,
+		    const struct ssl_iostream_ctx *ctx)
+{
+	(void)io;
+	(void)fd;
+	(void)mode;
+	(void)ctx;
+	unreachable();
+	return 0;
+}
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(ENABLE_SSL) */
diff --git a/src/lib/core/ssl_error.cc b/src/lib/core/ssl_error.cc
new file mode 100644
index 0000000000000000000000000000000000000000..00cdfe9e9e785176df3c885d90ebc3f22a1887c6
--- /dev/null
+++ b/src/lib/core/ssl_error.cc
@@ -0,0 +1,17 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#include "ssl_error.h"
+
+#include <stddef.h>
+
+#include "reflection.h"
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# error unimplemented
+#endif
+
+const struct type_info type_SSLError = make_type("SSLError", NULL);
diff --git a/src/lib/core/ssl_error.h b/src/lib/core/ssl_error.h
new file mode 100644
index 0000000000000000000000000000000000000000..a684cc066ea99ac47cba075d1dee990f8d7b1c35
--- /dev/null
+++ b/src/lib/core/ssl_error.h
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#pragma once
+
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# include "ssl_error_impl.h"
+#else /* !defined(ENABLE_SSL) */
+
+#include <stddef.h>
+
+#include "exception.h"
+#include "reflection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+extern const struct type_info type_SSLError;
+
+#if defined(__cplusplus)
+} /* extern "C" */
+
+class SSLError: public Exception {
+public:
+	SSLError(): Exception(&type_SSLError, NULL, 0) {}
+	virtual void raise() { throw this; }
+};
+
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(ENABLE_SSL) */
diff --git a/src/trivia/config.h.cmake b/src/trivia/config.h.cmake
index 2fafc46da59d809e8a1abcf29178688a0ff2e8e9..e299f5941636595af5ab0ede1db36859586896b0 100644
--- a/src/trivia/config.h.cmake
+++ b/src/trivia/config.h.cmake
@@ -256,6 +256,7 @@
 /* Cacheline size to calculate alignments */
 #define CACHELINE_SIZE 64
 
+#cmakedefine ENABLE_SSL 1
 #cmakedefine ENABLE_AUDIT_LOG 1
 #cmakedefine ENABLE_FEEDBACK_DAEMON 1
 
diff --git a/test/box-luatest/transport_test.lua b/test/box-luatest/transport_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..9664644a4c0c3d9863c30da2783d78b06812801b
--- /dev/null
+++ b/test/box-luatest/transport_test.lua
@@ -0,0 +1,77 @@
+local net = require('net.box')
+local server = require('test.luatest_helpers.server')
+local tarantool = require('tarantool')
+local t = require('luatest')
+local g = t.group()
+
+g.before_all = function()
+    g.server = server:new({alias = 'master'})
+    g.server:start()
+end
+
+g.after_all = function()
+    g.server:stop()
+end
+
+g.test_listen = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        local listen = box.cfg.listen
+        box.cfg({listen = {listen, params = {transport = 'plain'}}})
+        t.assert_error_msg_equals(
+            'Invalid transport: foo',
+            box.cfg, {listen = {listen, params = {transport = 'foo'}}})
+        box.cfg({listen = listen})
+    end)
+end
+
+g.test_replication = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        local listen = box.cfg.listen
+        box.cfg({replication = {listen, params = {transport = 'plain'}}})
+        t.assert_error_msg_equals(
+            'Invalid transport: foo',
+            box.cfg, {replication = {listen, params = {transport = 'foo'}}})
+        box.cfg({replication = {}})
+    end)
+end
+
+g.test_net_box = function()
+    local c = net.connect({g.server.net_box_uri,
+                           params = {transport = 'plain'}})
+    t.assert_equals(c.state, 'active')
+    c:close()
+    t.assert_error_msg_equals(
+        'Invalid transport: foo',
+        net.connect, {g.server.net_box_uri, params = {transport = 'foo'}})
+end
+
+-- Check that SSL isn't available in open-source builds.
+if tarantool.package ~= 'Tarantool Enterprise' then
+
+g.test_listen_ssl = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        t.assert_error_msg_equals(
+            'SSL is not available in this build',
+            box.cfg, {listen = 'localhost:0?transport=ssl'})
+    end)
+end
+
+g.test_replication_ssl = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        t.assert_error_msg_equals(
+            'SSL is not available in this build',
+            box.cfg, {replication = 'localhost:0?transport=ssl'})
+    end)
+end
+
+g.test_net_box_ssl = function()
+    t.assert_error_msg_equals(
+        'SSL is not available in this build',
+        net.connect, {g.server.net_box_uri, params = {transport = 'ssl'}})
+end
+
+end -- tarantool.package ~= 'Tarantool Enterprise'
diff --git a/test/unit/mp_error.cc b/test/unit/mp_error.cc
index 5e68b34d29f9052af4748bc11581d3b8f46c816c..777c68dff1844db220b28a290a7547b2188152f5 100644
--- a/test/unit/mp_error.cc
+++ b/test/unit/mp_error.cc
@@ -86,6 +86,7 @@ const char *standard_errors[] = {
 	"CollationError",
 	"SwimError",
 	"CryptoError",
+	"SSLError",
 };
 
 enum {
diff --git a/test/unit/mp_error.result b/test/unit/mp_error.result
index 356d2dc970ad9659f9cfa96ae5385c34e57794ff..3cb18996f326dd39e29a94ce928c234b73ba005a 100644
--- a/test/unit/mp_error.result
+++ b/test/unit/mp_error.result
@@ -1,7 +1,7 @@
 	*** main ***
 1..7
 	*** test_stack_error_decode ***
-    1..17
+    1..18
     ok 1 - check CustomError
     ok 2 - check AccessDeniedError
     ok 3 - check ClientError
@@ -18,7 +18,8 @@
     ok 14 - check CollationError
     ok 15 - check SwimError
     ok 16 - check CryptoError
-    ok 17 - stack size
+    ok 17 - check SSLError
+    ok 18 - stack size
 ok 1 - subtests
 	*** test_stack_error_decode: done ***
 	*** test_decode_unknown_type ***