diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -41,6 +41,13 @@
         "-conf=<file>", strprintf(_("Specify configuration file (default: %s)"),
                                   BITCOIN_CONF_FILENAME));
     strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
+    strUsage += HelpMessageOpt(
+        "-getinfo",
+        _("Get general information from the remote server. Note that unlike "
+          "server-side RPC calls, the results of -getinfo is the result of "
+          "multiple non-atomic requests. Some entries in the result may "
+          "represent results from different states (e.g. wallet balance may be "
+          "as of a different block from the chain state reported)"));
     AppendParamsHelpMessages(strUsage);
     strUsage += HelpMessageOpt(
         "-named",
@@ -223,7 +230,113 @@
 }
 #endif
 
-static UniValue CallRPC(const std::string &strMethod, const UniValue &params) {
+/**
+ * Class that handles the conversion from a command-line to a JSON-RPC request,
+ * as well as converting back to a JSON object that can be shown as result.
+ */
+class BaseRequestHandler {
+public:
+    virtual UniValue PrepareRequest(const std::string &method,
+                                    const std::vector<std::string> &args) = 0;
+    virtual UniValue ProcessReply(const UniValue &batch_in) = 0;
+};
+
+/** Process getinfo requests */
+class GetinfoRequestHandler : public BaseRequestHandler {
+public:
+    const int ID_NETWORKINFO = 0;
+    const int ID_BLOCKCHAININFO = 1;
+    const int ID_WALLETINFO = 2;
+
+    /** Create a simulated `getinfo` request. */
+    UniValue PrepareRequest(const std::string &method,
+                            const std::vector<std::string> &args) override {
+        UniValue result(UniValue::VARR);
+        result.push_back(
+            JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
+        result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue,
+                                           ID_BLOCKCHAININFO));
+        result.push_back(
+            JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
+        return result;
+    }
+
+    /** Collect values from the batch and form a simulated `getinfo` reply. */
+    UniValue ProcessReply(const UniValue &batch_in) override {
+        UniValue result(UniValue::VOBJ);
+        std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in, 3);
+        // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass
+        // them on getwalletinfo() is allowed to fail in case there is no
+        // wallet.
+        if (!batch[ID_NETWORKINFO]["error"].isNull()) {
+            return batch[ID_NETWORKINFO];
+        }
+        if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) {
+            return batch[ID_BLOCKCHAININFO];
+        }
+        result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]);
+        result.pushKV("protocolversion",
+                      batch[ID_NETWORKINFO]["result"]["protocolversion"]);
+        if (!batch[ID_WALLETINFO].isNull()) {
+            result.pushKV("walletversion",
+                          batch[ID_WALLETINFO]["result"]["walletversion"]);
+            result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]);
+        }
+        result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]);
+        result.pushKV("timeoffset",
+                      batch[ID_NETWORKINFO]["result"]["timeoffset"]);
+        result.pushKV("connections",
+                      batch[ID_NETWORKINFO]["result"]["connections"]);
+        result.pushKV("proxy",
+                      batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]);
+        result.pushKV("difficulty",
+                      batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
+        result.pushKV(
+            "testnet",
+            UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"].get_str() ==
+                     "test"));
+        if (!batch[ID_WALLETINFO].isNull()) {
+            result.pushKV("walletversion",
+                          batch[ID_WALLETINFO]["result"]["walletversion"]);
+            result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]);
+            result.pushKV("keypoololdest",
+                          batch[ID_WALLETINFO]["result"]["keypoololdest"]);
+            result.pushKV("keypoolsize",
+                          batch[ID_WALLETINFO]["result"]["keypoolsize"]);
+            if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
+                result.pushKV("unlocked_until",
+                              batch[ID_WALLETINFO]["result"]["unlocked_until"]);
+            }
+            result.pushKV("paytxfee",
+                          batch[ID_WALLETINFO]["result"]["paytxfee"]);
+        }
+        result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
+        result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
+        return JSONRPCReplyObj(result, NullUniValue, 1);
+    }
+};
+
+/** Process default single requests */
+class DefaultRequestHandler : public BaseRequestHandler {
+public:
+    UniValue PrepareRequest(const std::string &method,
+                            const std::vector<std::string> &args) override {
+        UniValue params;
+        if (gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
+            params = RPCConvertNamedValues(method, args);
+        } else {
+            params = RPCConvertValues(method, args);
+        }
+        return JSONRPCRequestObj(method, params, 1);
+    }
+
+    UniValue ProcessReply(const UniValue &reply) override {
+        return reply.get_obj();
+    }
+};
+
+static UniValue CallRPC(BaseRequestHandler *rh, const std::string &strMethod,
+                        const std::vector<std::string> &args) {
     std::string host;
     // In preference order, we choose the following for the port:
     //     1. -rpcport
@@ -280,8 +393,7 @@
         (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
 
     // Attach request data
-    std::string strRequest =
-        JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
+    std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
     struct evbuffer *output_buffer =
         evhttp_request_get_output_buffer(req.get());
     assert(output_buffer);
@@ -332,7 +444,7 @@
     if (!valReply.read(response.body)) {
         throw std::runtime_error("couldn't parse reply from server");
     }
-    const UniValue &reply = valReply.get_obj();
+    const UniValue reply = rh->ProcessReply(valReply);
     if (reply.empty()) {
         throw std::runtime_error(
             "expected reply to have result, error and id properties");
@@ -366,26 +478,27 @@
                 args.push_back(line);
             }
         }
-        if (args.size() < 1) {
-            throw std::runtime_error(
-                "too few parameters (need at least command)");
-        }
-        std::string strMethod = args[0];
-        // Remove trailing method name from arguments vector
-        args.erase(args.begin());
-
-        UniValue params;
-        if (gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
-            params = RPCConvertNamedValues(strMethod, args);
+        std::unique_ptr<BaseRequestHandler> rh;
+        std::string method;
+        if (gArgs.GetBoolArg("-getinfo", false)) {
+            rh.reset(new GetinfoRequestHandler());
+            method = "";
         } else {
-            params = RPCConvertValues(strMethod, args);
+            rh.reset(new DefaultRequestHandler());
+            if (args.size() < 1) {
+                throw std::runtime_error(
+                    "too few parameters (need at least command)");
+            }
+            method = args[0];
+            // Remove trailing method name from arguments vector
+            args.erase(args.begin());
         }
 
         // Execute and handle connection failures with -rpcwait
         const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
         do {
             try {
-                const UniValue reply = CallRPC(strMethod, params);
+                const UniValue reply = CallRPC(rh.get(), method, args);
 
                 // Parse reply
                 const UniValue &result = find_value(reply, "result");
diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -135,5 +135,7 @@
 bool GetAuthCookie(std::string *cookie_out);
 /** Delete RPC authentication cookie from disk */
 void DeleteAuthCookie();
+/** Parse JSON-RPC batch reply into a vector */
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num);
 
 #endif // BITCOIN_RPCPROTOCOL_H
diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp
--- a/src/rpc/protocol.cpp
+++ b/src/rpc/protocol.cpp
@@ -127,3 +127,22 @@
                   __func__, e.what());
     }
 }
+
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num) {
+    if (!in.isArray()) {
+        throw std::runtime_error("Batch must be an array");
+    }
+    std::vector<UniValue> batch(num);
+    for (size_t i = 0; i < in.size(); ++i) {
+        const UniValue &rec = in[i];
+        if (!rec.isObject()) {
+            throw std::runtime_error("Batch member must be object");
+        }
+        size_t id = rec["id"].get_int();
+        if (id >= num) {
+            throw std::runtime_error("Batch member id larger than size");
+        }
+        batch[id] = rec;
+    }
+    return batch;
+}
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -46,6 +46,35 @@
         assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli(
             '-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input="foo").echo)
 
+        self.log.info(
+            "Compare responses from `bitcoin-cli -getinfo` and the RPCs data is retrieved from.")
+        cli_get_info = self.nodes[0].cli('-getinfo').help()
+        wallet_info = self.nodes[0].getwalletinfo()
+        network_info = self.nodes[0].getnetworkinfo()
+        blockchain_info = self.nodes[0].getblockchaininfo()
+
+        assert_equal(cli_get_info['version'], network_info['version'])
+        assert_equal(cli_get_info['protocolversion'],
+                     network_info['protocolversion'])
+        assert_equal(cli_get_info['walletversion'],
+                     wallet_info['walletversion'])
+        assert_equal(cli_get_info['balance'], wallet_info['balance'])
+        assert_equal(cli_get_info['blocks'], blockchain_info['blocks'])
+        assert_equal(cli_get_info['timeoffset'], network_info['timeoffset'])
+        assert_equal(cli_get_info['connections'], network_info['connections'])
+        assert_equal(cli_get_info['proxy'],
+                     network_info['networks'][0]['proxy'])
+        assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty'])
+        assert_equal(cli_get_info['testnet'],
+                     blockchain_info['chain'] == "test")
+        assert_equal(cli_get_info['balance'], wallet_info['balance'])
+        assert_equal(cli_get_info['keypoololdest'],
+                     wallet_info['keypoololdest'])
+        assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize'])
+        assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee'])
+        assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
+        # unlocked_until is not tested because the wallet is not encrypted
+
 
 if __name__ == '__main__':
     TestBitcoinCli().main()