diff --git a/CMakeLists.txt b/CMakeLists.txt
index 72410085a9ee8570c387cadeb48c4dd07a9af15c..39428fc7170b0c504faee6cf53a2bd39c3afe211 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -261,6 +261,7 @@ add_subdirectory (algo)
 add_subdirectory (sim)
 add_subdirectory (reco)
 add_subdirectory (analysis)
+add_subdirectory (services)
 
 ### Others
 Option(LARGE_TEST_STATISTIC "Run the test suite with large statistic (100 events)" OFF)
diff --git a/services/CMakeLists.txt b/services/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e34c0786ba15c6f4787fff5ef585ae4efe471c1b
--- /dev/null
+++ b/services/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(histserv)
diff --git a/services/histserv/CMakeLists.txt b/services/histserv/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9bd62df1441dccb20c1caf2f6074f088a04208a0
--- /dev/null
+++ b/services/histserv/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(app)
+add_subdirectory(lib)
diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d5ca5d33d95f3b3f3ba0d51c9308c6ee413e12fa
--- /dev/null
+++ b/services/histserv/app/Application.cxx
@@ -0,0 +1,585 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#include "Application.h"
+
+#include "CbmFlesCanvasTools.h"
+
+#include <Logger.h>
+
+#include "TCanvas.h"
+#include "TEnv.h"
+#include "TFile.h"
+#include "TH1.h"
+#include "TH2.h"
+#include "THttpServer.h"
+#include "TMessage.h"
+#include "TObjArray.h"
+#include "TProfile.h"
+#include "TRootSniffer.h"
+#include "TSystem.h"
+
+#include <boost/serialization/utility.hpp>
+
+#include <mutex>
+
+#include "Histo1D.h"
+#include "ui_callbacks.h"
+
+std::mutex mtx;
+
+namespace cbm::services::histserv
+{
+
+  // -----   Constructor   ---------------------------------------------------------------------------------------------
+  Application::Application(ProgramOptions const& opt) : fOpt(opt)
+  {
+    /// Read options from executable
+    LOG(info) << "Options for Application.";
+    // FIXME: alternative to FairMQ fsChannelNameHistosInput  = fConfig->GetValue<std::string>("ChNameIn");
+    LOG(info) << " HTTP server port: " << fOpt.HttpPort();
+    if ("" != fOpt.HistoFile()) {  //
+      LOG(info) << "Output filename: " << fOpt.HistoFile() << (fOpt.Overwrite() ? " (in overwrite mode)" : "");
+    }
+
+    /// FIXME: SOMETHING_To_Replace_FairMQ!!!!!!!!!!!!!
+    /// FIXME: Initialize communication channels of SOMETHING_To_Replace_FairMQ
+    /// FIXME: Link channel to method in order to process received messages
+    /*
+    /// Method processing either combined Config+Data or Data only
+    OnData(fsChannelNameHistosInput, &CbmMqHistoServer::ReceiveConfigAndData);
+    * */
+
+    fServer = new THttpServer(Form("http:%u", fOpt.HttpPort()));
+    /// To avoid the server sucking all Histos from gROOT when no output file is used
+    fServer->GetSniffer()->SetScanGlobalDir(kFALSE);
+    const char* jsrootsys = gSystem->Getenv("JSROOTSYS");
+    if (!jsrootsys) jsrootsys = gEnv->GetValue("HttpServ.JSRootPath", jsrootsys);
+
+    fServer->RegisterCommand("/Reset_Hist", "bHistoServerResetHistos=true");
+    fServer->RegisterCommand("/Save_Hist", "bHistoServerSaveHistos=true");
+    fServer->RegisterCommand("/Stop_Server", "bHistoServerStop=true");
+
+    /*
+    fServer->RegisterCommand("/Reset_Hist", "this->ResetHistograms()");
+    fServer->RegisterCommand("/Save_Hist", "this->SaveHistograms()");
+    */
+
+    fServer->Restrict("/Reset_Hist", "allow=admin");
+    fServer->Restrict("/Save_Hist", "allow=admin");
+    fServer->Restrict("/Stop_Server", "allow=admin");
+
+
+    LOG(info) << "JSROOT location: " << jsrootsys;
+  }
+  // -------------------------------------------------------------------------------------------------------------------
+
+
+  // -----   Main Loop   -----------------------------------------------------------------------------------------------
+  void Application::Exec()
+  {
+    fStopThread = false;
+    fThread     = std::thread(&Application::UpdateHttpServer, this);
+
+    while (!bHistoServerStop) {  //
+      /// Infinite loop, this is a service which should survive until told otherwise after all
+
+      /// FIXME: Start listening to <SOMETHING?!?> to receive histograms and configuration
+
+      std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
+  }
+  // -------------------------------------------------------------------------------------------------------------------
+
+  // -----   Constructor   ---------------------------------------------------------------------------------------------
+  Application::~Application()
+  {
+    SaveHistograms();
+    fStopThread = true;
+    fThread.join();
+    SaveHistograms();
+  }
+  // -------------------------------------------------------------------------------------------------------------------
+
+  // -----   Server update background thread   -------------------------------------------------------------------------
+  void Application::UpdateHttpServer()
+  {
+    /// This is needed to have a reactive GUI independently of histogram updates reception
+    while (!fStopThread) {
+      std::this_thread::sleep_for(std::chrono::milliseconds(10));
+      std::lock_guard<std::mutex> lk(mtx);
+
+      fServer->ProcessRequests();
+
+      /// TODO: control flags communication from histo server to histograms sources?
+      /// Idea: 1 req channel (per process or not, mixup?), polling every N TS and/or M s
+      if (bHistoServerResetHistos) {
+        LOG(info) << "Reset Monitor histos ";
+        ResetHistograms();
+        bHistoServerResetHistos = false;
+      }  // if( bHistoServerResetHistos )
+
+      if (bHistoServerSaveHistos) {
+        LOG(info) << "Save All histos & canvases";
+        SaveHistograms();
+        bHistoServerSaveHistos = false;
+      }  // if( bHistoServerSaveHistos )
+    }
+  }
+  // -------------------------------------------------------------------------------------------------------------------
+
+  // -----   Server update background thread   -------------------------------------------------------------------------
+  bool Application::ReceiveData(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/)
+  {
+    LOG(debug) << "Application::ReceiveData => Processing histograms update";
+    TObject* tempObject = nullptr;
+
+    /// FIXME: Something to replace FairMQ and extract the histograms!!!!
+    /// FIXME: Need something to replace the ROOT serializer which allowed to have any of TH1x, TH2x, TH3x or TProfile
+    cbm::algo::Histo1D source(1, 0.0, 1.0, "dummy");
+
+    /// copied from CbmTaskDigiEventQa::ToTH1D
+    TH1D* result =
+      new TH1D(source.Name().c_str(), source.Name().c_str(), source.NumBins(), source.MinValue(), source.MaxValue());
+    for (uint32_t bin = 0; bin < source.NumBins(); bin++) {
+      result->SetBinContent(bin, source.Content(bin));
+    }
+    result->SetEntries(source.NumEntries());
+    tempObject = result;
+
+    if (TString(tempObject->ClassName()).EqualTo("TObjArray")) {
+      std::lock_guard<std::mutex> lk(mtx);
+      TObjArray* arrayHisto = static_cast<TObjArray*>(tempObject);
+      for (Int_t i = 0; i < arrayHisto->GetEntriesFast(); i++) {
+        TObject* pObj = arrayHisto->At(i);
+
+        if (nullptr != dynamic_cast<TProfile*>(pObj)) {
+          if (!ReadHistogram<TProfile>(dynamic_cast<TProfile*>(pObj))) {  //
+            return false;
+          }
+        }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
+        else if (nullptr != dynamic_cast<TH2*>(pObj)) {
+          if (!ReadHistogram<TH2>(dynamic_cast<TH2*>(pObj))) {  //
+            return false;
+          }
+        }  // if( nullptr != dynamic_cast< TH2 *>( pObj ) )
+        else if (nullptr != dynamic_cast<TH1*>(pObj)) {
+          if (!ReadHistogram<TH1>(dynamic_cast<TH1*>(pObj))) {  //
+            return false;
+          }
+        }  // if( nullptr != dynamic_cast< TH1 *>( pObj ) )
+        else
+          LOG(warning) << "Unsupported object type for " << pObj->GetName();
+      }  // for (Int_t i = 0; i < arrayHisto->GetEntriesFast(); i++)
+
+      LOG(debug) << "Application::ReceiveData => Deleting array";
+      /// Need to use Delete instead of Clear to avoid memory leak!!!
+      arrayHisto->Delete();
+
+      /// If new histos received, try to prepare as many canvases as possible
+      /// Should be expensive on start and cheap afterward
+      if (!fbAllCanvasReady) {
+        LOG(debug) << "Application::ReceiveData => Checking for canvases updates";
+        for (uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv) {
+          /// Jump canvases already ready
+          if (fvbCanvasReady[uCanv]) {  //
+            continue;
+          }
+
+          /// Now come the expensive part as we unpack its config and check each histo
+          fvbCanvasReady[uCanv] = PrepareCanvas(uCanv);
+        }  // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv )
+      }    // if( !fbAllCanvasReady )
+    }      // if (TString(tempObject->ClassName()).EqualTo("TObjArray"))
+    else {
+      fStopThread         = true;
+      std::string err_msg = "Application::ReceiveData => Wrong object type at input: ";
+      err_msg += tempObject->ClassName();
+      throw std::runtime_error(err_msg);
+    }
+
+    fNMessages += 1;
+
+    if (nullptr != tempObject) delete tempObject;
+
+    LOG(debug) << "Application::ReceiveData => Finished processing histograms update";
+
+    return true;
+  }
+
+  bool Application::ReceiveHistoConfig(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/)
+  {
+    std::pair<std::string, std::string> tempObject("", "");
+
+    /// FIXME: Something to replace FairMQ and extract the histograms!!!!
+    //  Deserialize<BoostSerializer<std::pair<std::string, std::string>>>(*msg, tempObject);
+    //  BoostSerializer<std::pair<std::string, std::string>>().Deserialize(*msg, tempObject);
+
+
+    LOG(info) << " Received configuration for histo " << tempObject.first << " : " << tempObject.second;
+
+    /// Check if histo name already received in previous messages
+    /// Linear search should be ok as config is shared only at startup
+    UInt_t uPrevHist = 0;
+    for (uPrevHist = 0; uPrevHist < fvpsHistosFolder.size(); ++uPrevHist) {
+      if (fvpsHistosFolder[uPrevHist].first == tempObject.first) {  //
+        break;
+      }
+    }  // for( UInt_t uPrevHist = 0; uPrevHist < fvpsHistosFolder.size(); ++uPrevHist )
+
+    if (uPrevHist < fvpsHistosFolder.size()) {
+      LOG(info) << " Ignored new configuration for histo " << tempObject.first
+                << " due to previously received one: " << tempObject.second;
+      /// Not sure if we should return false here...
+    }  // if( uPrevHist < fvpsHistosFolder.size() )
+    else {
+      fvpsHistosFolder.push_back(tempObject);
+      fvHistos.push_back(std::pair<TNamed*, std::string>(nullptr, ""));
+      fvbHistoRegistered.push_back(false);
+      fbAllHistosRegistered = false;
+    }  // else of if( uPrevHist < fvpsHistosFolder.size() )
+
+    return true;
+  }
+
+  bool Application::ReceiveCanvasConfig(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/)
+  {
+    std::pair<std::string, std::string> tempObject("", "");
+
+    /// FIXME: Something to replace FairMQ and extract the histograms!!!!
+    //  Deserialize<BoostSerializer<std::pair<std::string, std::string>>>(*msg, tempObject);
+    //  BoostSerializer<std::pair<std::string, std::string>>().Deserialize(*msg, tempObject);
+
+    LOG(info) << " Received configuration for canvas " << tempObject.first << " : " << tempObject.second;
+
+    /// Check if canvas name already received in previous messages
+    /// Linear search should be ok as config is shared only at startup
+    uint32_t uPrevCanv = 0;
+    for (uPrevCanv = 0; uPrevCanv < fvpsCanvasConfig.size(); ++uPrevCanv) {
+      if (fvpsCanvasConfig[uPrevCanv].first == tempObject.first) {  //
+        break;
+      }
+    }  // for( UInt_t uPrevCanv = 0; uPrevCanv < fvpsCanvasConfig.size(); ++uPrevCanv )
+
+    if (uPrevCanv < fvpsCanvasConfig.size()) {
+      LOG(warning) << " Ignored new configuration for Canvas " << tempObject.first
+                   << " due to previously received one: " << tempObject.second;
+      /// Not sure if we should return false here...
+    }  // if( uPrevCanv < fvpsCanvasConfig.size() )
+    else {
+      fvpsCanvasConfig.push_back(tempObject);
+      fvbCanvasReady.push_back(false);
+      fbAllCanvasReady = false;
+
+      fvCanvas.push_back(std::pair<TCanvas*, std::string>(nullptr, ""));
+      fvbCanvasRegistered.push_back(false);
+      fbAllCanvasRegistered = false;
+    }  // else of if( uPrevCanv < fvpsCanvasConfig.size() )
+    return true;
+  }
+
+  bool Application::ReceiveConfigAndData(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/)
+  {
+    /// FIXME: Something to replace FairMQ and extract the histograms!!!!
+    /*
+    /// Reject anything but
+    /// - Header + Histo Config + Canvas Config + Histo Data
+    /// - Histo data only
+    if (4 != parts.Size()) {
+      if (1 == parts.Size()) {
+        /// Catch case of having only the histograms updates
+        return ReceiveData(parts.At(0), 0);
+      }  // if( 1 == parts.Size() )
+
+      fStopThread = true;
+      std::string err_msg = "Application::ReceiveConfigAndData => Wrong number of parts: ";
+      err_msg += parts.Size();
+      err_msg += " instead of 4 (Header + Histo Config + Canvas config + Data) or 1 (Data)!";
+      throw std::runtime_error(err_msg);
+    }  // if( parts.Size() < 4 )
+
+    LOG(info) << "Application::ReceiveConfigAndData => Received composed message with " << parts.Size() << " parts";
+
+    /// Header contains a pair of
+    std::pair<uint32_t, uint32_t> pairHeader;
+    //  Deserialize<BoostSerializer<std::pair<uint32_t, uint32_t>>>(*parts.At(0), pairHeader);
+    BoostSerializer<std::pair<uint32_t, uint32_t>>().Deserialize(*parts.At(0), pairHeader);
+
+    LOG(info) << "Application::ReceiveConfigAndData => Received configuration for " << pairHeader.first
+              << " histos and " << pairHeader.second << " canvases";
+
+    uint32_t uOffsetHistoConfig = pairHeader.first;
+    if (0 == pairHeader.first) {
+      uOffsetHistoConfig = 1;
+      if (0 < (parts.At(uOffsetHistoConfig))->GetSize()) {
+        fStopThread = true;
+        std::string err_msg = "Application::ReceiveConfigAndData => No histo config expected but corresponding message";
+        err_msg += " is not empty: ";
+        err_msg += (parts.At(uOffsetHistoConfig))->GetSize();
+        throw std::runtime_error(err_msg);
+      }
+    }
+
+    uint32_t uOffsetCanvasConfig = pairHeader.second;
+    if (0 == pairHeader.second) {
+      uOffsetCanvasConfig = 1;
+      if (0 < (parts.At(uOffsetHistoConfig + uOffsetCanvasConfig))->GetSize()) {
+        fStopThread = true;
+        std::string err_msg = "Application::ReceiveConfigAndData => No Canvas config expected but corresponding message";
+        err_msg += " is not empty: ";
+        err_msg += (parts.At(uOffsetHistoConfig + uOffsetCanvasConfig))->GetSize();
+        throw std::runtime_error(err_msg);
+      }
+    }
+
+    if (static_cast<size_t>(parts.Size()) != 1 + uOffsetHistoConfig + uOffsetCanvasConfig + 1) {
+      fStopThread = true;
+      std::string err_msg = "Application::ReceiveConfigAndData => Number of parts not matching header: ";
+      err_msg += parts.Size();
+      err_msg += " instead of ";
+      err_msg += 1 + uOffsetHistoConfig + uOffsetCanvasConfig + 1;
+      throw std::runtime_error(err_msg);
+    }  // if( parts.Size() != 1 + pairHeader.first + pairHeader.second )
+
+    /// Decode parts for histograms configuration
+    for (uint32_t uHisto = 0; uHisto < pairHeader.first; ++uHisto) {
+      ReceiveHistoConfig(parts.At(1 + uHisto), 0);
+    }  // for (UInt_t uHisto = 0; uHisto < pairHeader.first; ++uHisto)
+
+    /// Decode parts for histograms configuration
+    for (uint32_t uCanv = 0; uCanv < pairHeader.second; ++uCanv) {
+      ReceiveCanvasConfig(parts.At(1 + uOffsetHistoConfig + uCanv), 0);
+    }  // for (UInt_t uCanv = 0; uCanv < pairHeader.second; ++uCanv)
+
+    /// Decode the histograms data now that the configuration is loaded
+    ReceiveData(parts.At(1 + uOffsetHistoConfig + uOffsetCanvasConfig), 0);
+    *
+    LOG(info) << "Application::ReceiveConfigAndData => Finished processing  composed message with " << parts.Size()
+              << " parts";
+    */
+
+    return true;
+  }
+  // -------------------------------------------------------------------------------------------------------------------
+
+  template<class HistoT>
+  bool Application::ReadHistogram(HistoT* pHist)
+  {
+    int index1 = FindHistogram(pHist->GetName());
+    if (-1 == index1) {
+      HistoT* histogram_new = static_cast<HistoT*>(pHist->Clone());
+      fArrayHisto.Add(histogram_new);
+
+      LOG(info) << "Received new histo " << pHist->GetName();
+
+      /// If new histo received, try to register it if configuration available
+      if (!fbAllHistosRegistered) {
+        for (uint32_t uHist = 0; uHist < fvpsHistosFolder.size(); ++uHist) {
+          /// Jump histos already ready
+          if (fvbHistoRegistered[uHist]) {  //
+            continue;
+          }
+
+          /// Check if name matches one in config for others
+          if (fvpsHistosFolder[uHist].first == histogram_new->GetName()) {
+            fvHistos[uHist] = std::pair<TNamed*, std::string>(histogram_new, fvpsHistosFolder[uHist].second);
+            fServer->Register(Form("/%s", fvHistos[uHist].second.data()), fvHistos[uHist].first);
+            fvbHistoRegistered[uHist] = true;
+
+            LOG(info) << "registered histo " << fvHistos[uHist].first->GetName() << " in folder "
+                      << fvHistos[uHist].second;
+
+
+            /// Update flag telling whether all known histos are registered
+            fbAllHistosRegistered = true;
+            for (uint32_t uIdx = 0; uIdx < fvbHistoRegistered.size(); ++uIdx) {
+              if (!fvbHistoRegistered[uIdx]) {
+                fbAllHistosRegistered = false;
+                break;
+              }  // if( !fvbHistoRegistered[ uIdx ] )
+            }    // for( uint32_t uIdx = 0; uIdx < fvbHistoRegistered.size(); ++uIdx )
+
+            break;
+          }  // if( fvpsHistosFolder[ uHist ].first == histogram_new->GetName() )
+        }    // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv )
+      }      // if( !fbAllCanvasReady )
+    }        // if (-1 == index1)
+    else {
+      LOG(debug) << "Received update for: " << pHist->GetName();
+      HistoT* histogram_existing = dynamic_cast<HistoT*>(fArrayHisto.At(index1));
+      if (nullptr == histogram_existing) {
+        LOG(error) << "CbmMqHistoServer::ReadHistogram => "
+                   << "Incompatible type found during update for histo " << pHist->GetName();
+        return false;
+      }  // if( nullptr == histogram_existing )
+
+      histogram_existing->Add(pHist);
+    }  // else of if (-1 == index1)
+    return true;
+  }
+
+  int Application::FindHistogram(const std::string& name)
+  {
+    for (int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist) {
+      TObject* obj = fArrayHisto.At(iHist);
+      if (TString(obj->GetName()).EqualTo(name)) {  //
+        return iHist;
+      }
+    }  // for( int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist )
+    return -1;
+  }
+
+  bool Application::PrepareCanvas(uint32_t uCanvIdx)
+  {
+    LOG(debug) << " Extracting configuration for canvas index " << uCanvIdx;
+    CanvasConfig conf(ExtractCanvasConfigFromString(fvpsCanvasConfig[uCanvIdx].second));
+
+    /// First check if all objects to be drawn are present
+    uint32_t uNbPads(conf.GetNbPads());
+    for (uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx) {
+      uint32_t uNbObj(conf.GetNbObjsInPad(uPadIdx));
+      for (uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx) {
+        std::string sName(conf.GetObjName(uPadIdx, uObjIdx));
+        /// Check for empty pads!
+        if ("nullptr" != sName) {
+          if (FindHistogram(sName) < 0) {
+            return false;
+          }  // if( FindHistogram( conf.GetObjName( uPadIdx, uObjIdx ) ) < 0 )
+        }    // if( "nullptr" != sName )
+      }      // for( uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx )
+    }        // for( uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx )
+
+    LOG(info) << " All histos found for canvas " << conf.GetName().data() << ", now preparing it";
+
+    /// Create new canvas and pads
+    TCanvas* pNewCanv = new TCanvas(conf.GetName().data(), conf.GetTitle().data());
+    pNewCanv->Divide(conf.GetNbPadsX(), conf.GetNbPadsY());
+
+    /// Loop on pads
+    for (uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx) {
+      pNewCanv->cd(1 + uPadIdx);
+
+      /// Pad settings
+      gPad->SetGrid(conf.GetGridx(uPadIdx), conf.GetGridy(uPadIdx));
+      gPad->SetLogx(conf.GetLogx(uPadIdx));
+      gPad->SetLogy(conf.GetLogy(uPadIdx));
+      gPad->SetLogz(conf.GetLogz(uPadIdx));
+
+      /// Add objects (we know they are there
+      uint32_t uNbObj(conf.GetNbObjsInPad(uPadIdx));
+      for (uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx) {
+        std::string sName(conf.GetObjName(uPadIdx, uObjIdx));
+        if ("nullptr" != sName) {
+          TObject* pObj = fArrayHisto[FindHistogram(sName)];
+
+          if (nullptr != dynamic_cast<TProfile*>(pObj)) {
+            dynamic_cast<TProfile*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+          }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
+          else if (nullptr != dynamic_cast<TH2*>(pObj)) {
+            dynamic_cast<TH2*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+          }  // if( nullptr != dynamic_cast< TH2 *>( pObj ) )
+          else if (nullptr != dynamic_cast<TH1*>(pObj)) {
+            dynamic_cast<TH1*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+          }  // if( nullptr != dynamic_cast< TH1 *>( pObj ) )
+          else
+            LOG(warning) << "  Unsupported object type for " << sName << " when preparing canvas " << conf.GetName();
+
+          LOG(info) << "  Configured histo " << sName << " on pad " << (1 + uPadIdx) << " for canvas "
+                    << conf.GetName().data();
+        }  // if( "nullptr" != sName )
+      }    // for( uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx )
+    }      // for( uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx )
+
+    fvCanvas[uCanvIdx] = std::pair<TCanvas*, std::string>(pNewCanv, "canvases");
+    fServer->Register(Form("/%s", fvCanvas[uCanvIdx].second.data()), fvCanvas[uCanvIdx].first);
+    fvbCanvasRegistered[uCanvIdx] = true;
+
+    LOG(info) << " Registered canvas " << fvCanvas[uCanvIdx].first->GetName() << " in folder "
+              << fvCanvas[uCanvIdx].second;
+
+    /// Update flag telling whether all known canvases are registered
+    fbAllCanvasRegistered = true;
+    for (uint32_t uIdx = 0; uIdx < fvbCanvasRegistered.size(); ++uIdx) {
+      if (!fvbCanvasRegistered[uIdx]) {
+        fbAllCanvasRegistered = false;
+        break;
+      }  // if( !fvbCanvasRegistered[ uIdx ] )
+    }    // for( uint32_t uIdx = 0; uIdx < fvbCanvasRegistered.size(); ++uIdx )
+
+    return true;
+  }
+
+  bool Application::ResetHistograms()
+  {
+    for (int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist) {
+      dynamic_cast<TH1*>(fArrayHisto.At(iHist))->Reset();
+    }  // for( int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist )
+    return true;
+  }
+
+  bool Application::SaveHistograms()
+  {
+    if ("" == fOpt.HistoFile()) {  //
+      LOG(error) << "Filename for saving histograms and canvases not defined. Ignoring request.";
+      return false;
+    }
+
+    /// Save old global file and folder pointer to avoid messing with FairRoot
+    TFile* oldFile     = gFile;
+    TDirectory* oldDir = gDirectory;
+
+    /// (Re-)Create ROOT file to store the histos
+    TFile* histoFile = nullptr;
+
+    // open separate histo file in recreate mode
+    histoFile = new TFile(fOpt.HistoFile().data(), fOpt.Overwrite() ? "RECREATE" : "CREATE");
+
+    if (nullptr == histoFile) {  //
+      gFile      = oldFile;
+      gDirectory = oldDir;
+      LOG(error) << "Ignoring request to save histograms and canvases: could not open output file " << fOpt.HistoFile();
+      return false;
+    }
+
+    LOG(info) << "Saving Histograms and canvases in file: " << fOpt.HistoFile();
+
+    /// Register the histos in the HTTP server
+    for (UInt_t uHisto = 0; uHisto < fvHistos.size(); ++uHisto) {
+      /// Make sure we end up in chosen folder
+      TString sFolder = fvHistos[uHisto].second.data();
+      if (nullptr == gDirectory->Get(sFolder)) {  //
+        gDirectory->mkdir(sFolder);
+      }
+      gDirectory->cd(sFolder);
+
+      /// Write plot
+      fvHistos[uHisto].first->Write();
+
+      histoFile->cd();
+    }  // for( UInt_t uHisto = 0; uHisto < fvHistos.size(); ++uHisto )
+
+    for (UInt_t uCanvas = 0; uCanvas < fvCanvas.size(); ++uCanvas) {
+      /// Make sure we end up in chosen folder
+      TString sFolder = fvCanvas[uCanvas].second.data();
+      if (nullptr == gDirectory->Get(sFolder)) {  //
+        gDirectory->mkdir(sFolder);
+      }
+      gDirectory->cd(sFolder);
+
+      /// Write plot
+      fvCanvas[uCanvas].first->Write();
+
+      histoFile->cd();
+    }  // for( UInt_t uHisto = 0; uHisto < fvCanvas.size(); ++uHisto )
+
+    /// Restore old global file and folder pointer to avoid messing with FairRoot
+    gFile      = oldFile;
+    gDirectory = oldDir;
+
+    histoFile->Close();
+
+    return true;
+  }
+
+}  // namespace cbm::services::histserv
diff --git a/services/histserv/app/Application.h b/services/histserv/app/Application.h
new file mode 100644
index 0000000000000000000000000000000000000000..2b8168df58fdbc9c9dba17d0f659ad759d2e1bd3
--- /dev/null
+++ b/services/histserv/app/Application.h
@@ -0,0 +1,94 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#ifndef CBM_SERVICES_HISTSERV_APP_APPLICATION_H
+#define CBM_SERVICES_HISTSERV_APP_APPLICATION_H 1
+
+#include "THttpServer.h"
+#include "TObjArray.h"
+
+#include <memory>
+#include <string>
+#include <thread>
+
+#include "ProgramOptions.h"
+
+class TNamed;
+class TCanvas;
+
+namespace cbm::services::histserv
+{
+
+  class Application {
+
+
+  public:
+    /** @brief Standard constructor, initialises the application
+     ** @param opt  **/
+    explicit Application(ProgramOptions const& opt);
+
+    /** @brief Copy constructor forbidden **/
+    Application(const Application&) = delete;
+
+    /** @brief Assignment operator forbidden **/
+    void operator=(const Application&) = delete;
+
+    /** @brief Destructor **/
+    ~Application();
+
+    /** @brief Run the application **/
+    void Exec();
+
+    void UpdateHttpServer();
+
+  private:
+    const std::string& OutputFile() const;
+    const std::string& ParamFile() const;
+    const std::string& SetupTag() const;
+    //const std::string& ConfigFile() const;
+
+    bool ReceiveData(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/);
+    bool ReceiveHistoConfig(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/);
+    bool ReceiveCanvasConfig(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/);
+    bool ReceiveConfigAndData(/*FIXME_SOMETHING_To_Replace_FairMQ& parts*/);
+
+    template<class HistoT>
+    bool ReadHistogram(HistoT* pHist);
+    int FindHistogram(const std::string& name);
+    bool PrepareCanvas(uint32_t uCanvIdx);
+
+    bool ResetHistograms();
+    bool SaveHistograms();
+
+  private:
+    ProgramOptions const& fOpt;      ///< Program options object
+    THttpServer* fServer = nullptr;  ///< ROOT Histogram server (JSroot)
+    std::thread fThread;
+    bool fStopThread = false;
+
+    /// Array of histograms with unique names
+    TObjArray fArrayHisto;
+    /// Vector of string with ( HistoName, FolderPath ) to configure the histogram
+    std::vector<std::pair<std::string, std::string>> fvpsHistosFolder = {};
+    /// Vector of string pairs with ( CanvasName, CanvasConfig ) to configure the canvases and histos within
+    /// Format of Can config is "Name;Title;NbPadX(U);NbPadY(U);ConfigPad1(s);....;ConfigPadXY(s)"
+    /// Format of Pad config is "GrixX(b),GridY(b),LogX(b),LogY(b),LogZ(b),HistoName(s),DrawOptions(s)"
+    std::vector<std::pair<std::string, std::string>> fvpsCanvasConfig = {};
+    std::vector<bool> fvbCanvasReady                                  = {};
+    bool fbAllCanvasReady                                             = false;
+
+    std::vector<std::pair<TNamed*, std::string>> fvHistos  = {};  ///< Vector of Histos pointers and folder path
+    std::vector<bool> fvbHistoRegistered                   = {};
+    bool fbAllHistosRegistered                             = false;
+    std::vector<std::pair<TCanvas*, std::string>> fvCanvas = {};  ///< Vector of Canvas pointers and folder path
+    std::vector<bool> fvbCanvasRegistered                  = {};
+    bool fbAllCanvasRegistered                             = false;
+
+    /// Internal status
+    uint32_t fNMessages = 0;
+  };
+
+}  // namespace cbm::services::histserv
+
+#endif /* CBM_SERVICES_HISTSERV_APP_APPLICATION_H */
diff --git a/services/histserv/app/CMakeLists.txt b/services/histserv/app/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b614872a252d4a900e0b7d2ca64a2a34822da4c4
--- /dev/null
+++ b/services/histserv/app/CMakeLists.txt
@@ -0,0 +1,43 @@
+# CMakeList file for binary histserv_nofairmq
+# P.-A. Loizeau,     26 June 2023
+
+set(INCLUDE_DIRECTORIES
+  ${INCLUDE_DIRECTORIES}
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${CBMROOT_SOURCE_DIR}/algo/qa/
+  )
+
+set(SRCS
+  Application.cxx
+  ProgramOptions.cxx
+  main.cxx
+  )
+
+set(HEADERS
+  Application.h
+  ProgramOptions.h
+ )
+
+set(INCLUDE_DIRECTORIES
+  ${INCLUDE_DIRECTORIES}
+  ${CBMROOT_SOURCE_DIR}
+  )
+
+add_executable(histserv_nofairmq ${SRCS} ${HEADERS})
+
+target_link_libraries(histserv_nofairmq
+  PUBLIC
+    Algo
+    CbmFlibFlesTools
+    CbmServicesHistServ
+  PRIVATE
+    Boost::program_options
+    FairLogger::FairLogger
+    ROOT::Core
+    ROOT::Gpad
+    ROOT::Hist
+    ROOT::RIO
+    ROOT::RHTTP
+  )
+
+install(TARGETS histserv_nofairmq DESTINATION bin)
diff --git a/services/histserv/app/ProgramOptions.cxx b/services/histserv/app/ProgramOptions.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d5826bbe284c49fb3331d05d9b862264729a134c
--- /dev/null
+++ b/services/histserv/app/ProgramOptions.cxx
@@ -0,0 +1,69 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#include "ProgramOptions.h"
+
+#include <Logger.h>
+
+#include <boost/program_options.hpp>
+
+#include <iostream>
+
+namespace po = boost::program_options;
+
+using std::string;
+
+namespace cbm::services::histserv
+{
+
+  // -----   Parse command line   ---------------------------------------------
+  void ProgramOptions::ParseOptions(int argc, char* argv[])
+  {
+
+    // --- Define generic options
+    po::options_description generic("Generic options");
+    auto generic_add = generic.add_options();
+    generic_add("help,h", "display this help and exit");
+
+    // --- Build default config file path
+    //string defconfig = std::getenv("VMCWORKDIR");
+    //defconfig.append("/");
+    //defconfig.append(DEFAULT_CONFIG);
+
+    // --- Define configuration options
+    po::options_description config("Configuration");
+    auto config_add = config.add_options();
+    config_add("input,i", po::value<string>(&fsChanHistosIn)->value_name("<???????>"),
+               "name or host:port or whatever is needed for input channel (histos/canvases config and data)");
+    config_add("port,p", po::value<uint32_t>(&fuHttpServerPort)->default_value(8080),
+               "port on which the http ROOT server (JSroot) will be available");
+    config_add("output,o", po::value<string>(&fsHistoFileName)->value_name("<file name>"),
+               "name of the output ROOT file with histograms backup");
+    // config_add("config,c", po::value<string>(&fConfig)->value_name("<file name>")->default_value(defconfig),
+    //            "name of a YAML file describing the configuration of reconstruction");
+    config_add("overwrite,w", po::bool_switch(&fOverwrite)->default_value(false),
+               "allow to overwite an existing output file");
+
+    // --- Allowed options
+    po::options_description cmdline_options("Allowed options");
+    cmdline_options.add(generic).add(config);
+
+    // --- Parse command line
+    po::variables_map vars;
+    po::store(po::parse_command_line(argc, argv, cmdline_options), vars);
+    po::notify(vars);
+
+    // --- Help: print help information and exit program
+    if (vars.count("help") != 0u) {
+      std::cout << cmdline_options << std::endl;
+      exit(EXIT_SUCCESS);
+    }
+
+    // --- Catch mandatory parameters not being specified
+    if (vars.count("input") == 0u) { throw std::runtime_error("no input channel specified"); }
+    if (vars.count("port") == 0u) { throw std::runtime_error("no http port specified"); }
+  }
+  // --------------------------------------------------------------------------
+
+}  // namespace cbm::services::histserv
diff --git a/services/histserv/app/ProgramOptions.h b/services/histserv/app/ProgramOptions.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e7a526bc265ac074b1fd4832382f4c705ac7d2a
--- /dev/null
+++ b/services/histserv/app/ProgramOptions.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#ifndef CBM_SERVICES_HISTSERV_APP_PROGRAMOPTIONS_H
+#define CBM_SERVICES_HISTSERV_APP_PROGRAMOPTIONS_H 1
+
+#include <string>
+
+namespace cbm::services::histserv
+{
+
+  /** @class ProgramOptions
+   ** @author Pierre-Alain Loizeau <p.-a.loizeau@gsi.de>
+   ** @since 26 June 2023
+   **
+   ** Program option class for the application histserv_nofairmq
+   **/
+  class ProgramOptions {
+  public:
+    /** @brief Standard constructor with command line arguments **/
+    ProgramOptions(int argc, char* argv[]) { ParseOptions(argc, argv); }
+
+    /** @brief Copy constructor forbidden **/
+    ProgramOptions(const ProgramOptions&) = default;
+
+    /** @brief Assignment operator forbidden **/
+    ProgramOptions& operator=(const ProgramOptions&) = default;
+
+    /** @brief Destructor **/
+    ~ProgramOptions() = default;
+
+    /** @brief Get interface channel name or hostname + port or whatever or ????? (FIXME: replacement of FairMQ) **/
+    [[nodiscard]] const std::string& ComChan() const { return fsChanHistosIn; }
+
+    /** @brief Get histo server http port **/
+    [[nodiscard]] const uint32_t& HttpPort() const { return fuHttpServerPort; }
+
+    /** @brief Get output file name (.root format) **/
+    [[nodiscard]] const std::string& HistoFile() const { return fsHistoFileName; }
+
+    /** @brief Get overwrite option **/
+    [[nodiscard]] bool Overwrite() const { return fOverwrite; }
+
+    // /** @brief Get configuration file name (YAML format) **/
+    // [[nodiscard]] const std::string& ConfigFile() const { return fConfig; }
+
+  private:
+    /** @brief Parse command line arguments using boost program_options **/
+    void ParseOptions(int argc, char* argv[]);
+
+
+  private:  // members
+    std::string fsChanHistosIn  = "histogram-in";
+    uint32_t fuHttpServerPort   = 8080;                ///< HTTP port of the ROOT web server
+    std::string fsHistoFileName = "histos_dump.root";  ///< Output file name (ROOT format)
+    bool fOverwrite             = false;               ///< Enable overwriting of existing output file
+    //std::string fConfig       = "";         ///< Configuration file name (YAML format)
+  };
+
+}  // namespace cbm::services::histserv
+
+#endif /* CBM_SERVICES_HISTSERV_APP_PROGRAMOPTIONS_H */
diff --git a/services/histserv/app/main.cxx b/services/histserv/app/main.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4dbaa8862b133acb0bcdf344088ea1fe386f0b77
--- /dev/null
+++ b/services/histserv/app/main.cxx
@@ -0,0 +1,26 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+#include <Logger.h>
+
+#include "Application.h"
+#include "ProgramOptions.h"
+
+using namespace cbm::services::histserv;
+
+int main(int argc, char* argv[])
+{
+  LOG(info) << "*****   Histogram server without FairMQ   *****";
+  try {
+    ProgramOptions opt(argc, argv);
+    Application app(opt);
+    app.Exec();
+  }
+  catch (std::exception const& e) {
+    LOG(error) << e.what() << "; terminating.";
+    return EXIT_FAILURE;
+  }
+
+  LOG(info) << "Histogram server without FairMQ: Program completed successfully; exiting.";
+  return EXIT_SUCCESS;
+}
diff --git a/services/histserv/lib/CMakeLists.txt b/services/histserv/lib/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..790bd92aa1f56383a3e20681833cf981c3117b5e
--- /dev/null
+++ b/services/histserv/lib/CMakeLists.txt
@@ -0,0 +1,21 @@
+# CMakeList file for library associated to HistServ service, needed by CLING to allow commands in WebUI
+# P.-A. Loizeau,     26 June 2023
+
+set(INCLUDE_DIRECTORIES
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  )
+
+#set(SRCS
+#  ui_callbacks.cxx
+#  )
+
+list(APPEND HEADERS ui_callbacks.h)
+
+set(LIBRARY_NAME CbmServicesHistServ)
+set(LINKDEF ${LIBRARY_NAME}LinkDef.h )
+
+set(PUBLIC_DEPENDENCIES
+  ROOT::Core
+  )
+
+generate_cbm_library()
diff --git a/services/histserv/lib/CbmServicesHistServLinkDef.h b/services/histserv/lib/CbmServicesHistServLinkDef.h
new file mode 100644
index 0000000000000000000000000000000000000000..843a69aa136009d78e0401ca8013ff16c8c47de0
--- /dev/null
+++ b/services/histserv/lib/CbmServicesHistServLinkDef.h
@@ -0,0 +1,11 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#ifdef __CINT__
+
+#pragma link off all globals;
+#pragma link off all classes;
+#pragma link off all functions;
+
+#endif
diff --git a/services/histserv/lib/ui_callbacks.h b/services/histserv/lib/ui_callbacks.h
new file mode 100644
index 0000000000000000000000000000000000000000..a658747a76f8da3eb77474a7ae974b5d5b584945
--- /dev/null
+++ b/services/histserv/lib/ui_callbacks.h
@@ -0,0 +1,12 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#ifndef CBM_SERVICES_HISTSERV_LIB_UI_CALLBACKS_H
+#define CBM_SERVICES_HISTSERV_LIB_UI_CALLBACKS_H 1
+
+static bool bHistoServerResetHistos = false;
+static bool bHistoServerSaveHistos  = false;
+static bool bHistoServerStop        = false;
+
+#endif /* CBM_SERVICES_HISTSERV_LIB_UI_CALLBACKS_H */